From 79e379b61a5ea099706570ec34eab861203d90fd Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 10 Jul 2025 10:12:50 -0400 Subject: [PATCH] feature/IO-3291-Tasks-Notifications: Checkpoint --- .../task-center/task-center.component.jsx | 151 ++++++++++-------- .../task-center/task-center.container.jsx | 14 +- .../task-center/task-center.styles.scss | 94 +++++------ 3 files changed, 149 insertions(+), 110 deletions(-) diff --git a/client/src/components/task-center/task-center.component.jsx b/client/src/components/task-center/task-center.component.jsx index b2466adda..705a2bb0b 100644 --- a/client/src/components/task-center/task-center.component.jsx +++ b/client/src/components/task-center/task-center.component.jsx @@ -13,7 +13,7 @@ import { } from "@ant-design/icons"; const TaskCenterComponent = forwardRef( - ({ visible, tasks, loading, onTaskClick, onLoadMore, totalTasks, createNewTask }, ref) => { + ({ visible, tasks, loading, error, onTaskClick, onLoadMore, hasMore, createNewTask }, ref) => { const { t } = useTranslation(); const virtuosoRef = useRef(null); @@ -24,8 +24,8 @@ const TaskCenterComponent = forwardRef( [t("tasks.labels.no_due_date")]: }; - const groupedItems = useMemo(() => { - const now = day("2025-07-09"); // Set to current date + const groups = useMemo(() => { + const now = day(); const today = now.startOf("day"); const overdue = tasks.filter((t) => t.due_date && day(t.due_date).isBefore(today)); @@ -35,62 +35,79 @@ const TaskCenterComponent = forwardRef( ); const noDueDate = tasks.filter((t) => !t.due_date); - const makeGroup = (label, data) => (data.length ? [{ type: "section", label, tasks: data }] : []); - return [ - ...makeGroup(t("tasks.labels.overdue"), overdue), - ...makeGroup(t("tasks.labels.due_today"), dueToday), - ...makeGroup(t("tasks.labels.upcoming"), upcoming), - ...makeGroup(t("tasks.labels.no_due_date"), noDueDate) - ]; + { label: t("tasks.labels.overdue"), tasks: overdue }, + { label: t("tasks.labels.due_today"), tasks: dueToday }, + { label: t("tasks.labels.upcoming"), tasks: upcoming }, + { label: t("tasks.labels.no_due_date"), tasks: noDueDate } + ].filter((group) => group.tasks.length > 0); }, [tasks, t]); - const getPriorityColor = (priority) => { - switch (priority) { - case 1: - return "red"; - case 2: - return "orange"; - case 3: - return "green"; - default: - return null; - } + const groupCounts = useMemo(() => groups.map((group) => group.tasks.length), [groups]); + + const flatTasks = useMemo(() => groups.flatMap((group) => group.tasks), [groups]); + + const priorityColors = { + 1: "red", + 2: "orange", + 3: "green" }; - const renderSection = (section, index) => ( -
+ const getPriorityColor = (priority) => priorityColors[priority] || null; + + const groupContent = (groupIndex) => { + const { label } = groups[groupIndex]; + return (
- {sectionIcons[section.label]} - {section.label} ({section.tasks.length}) + {sectionIcons[label]} + {label} ({groups[groupIndex].tasks.length})
- - - {section.tasks.map((task) => { - const priorityColor = getPriorityColor(task.priority); - return ( - onTaskClick(task.id)}> - - - - ); - })} - -
-
-
{task.title}
-
- {t("notifications.labels.ro-number", { - ro_number: task.job?.ro_number || t("general.labels.na") - })} -
-
-
- {task.due_date && {day(task.due_date).fromNow()}} - {!!priorityColor && } -
-
- ); + ); + }; + + const itemContent = (index) => { + const task = flatTasks[index]; + const priorityColor = getPriorityColor(task.priority); + return ( +
onTaskClick(task.id)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + onTaskClick(task.id); + } + }} + > +
+
+
{task.title}
+
+ {t("notifications.labels.ro-number", { + ro_number: task.job?.ro_number || t("general.labels.na") + })} +
+
+
+
+ {task.due_date && {day(task.due_date).fromNow()}} + {!!priorityColor && } +
+
+ ); + }; + + if (error) { + return ( +
+
+

{t("tasks.labels.my_tasks_center")}

+
+
{t("errors.tasks_load_failed")}
+
+ ); + } return (
@@ -104,17 +121,25 @@ const TaskCenterComponent = forwardRef(
- renderSection(section, index)} - /> - - {tasks.length < totalTasks && ( - + {tasks.length === 0 && !loading ? ( +
{t("tasks.labels.no_tasks")}
+ ) : ( + + loading ? ( +
+ +
+ ) : null + }} + /> )} ); diff --git a/client/src/components/task-center/task-center.container.jsx b/client/src/components/task-center/task-center.container.jsx index 66681fdce..f05499e95 100644 --- a/client/src/components/task-center/task-center.container.jsx +++ b/client/src/components/task-center/task-center.container.jsx @@ -32,7 +32,11 @@ const TaskCenterContainer = ({ visible, onClose, bodyshop, currentUser, setTaskU }, [bodyshop, currentUser]); // Query 1: Tasks with due dates - const { data: dueDateData, loading: dueLoading } = useQuery(QUERY_TASKS_WITH_DUE_DATES, { + const { + data: dueDateData, + loading: dueLoading, + error: dueError + } = useQuery(QUERY_TASKS_WITH_DUE_DATES, { variables: { bodyshop: bodyshop?.id, assigned_to: assignedToId, @@ -47,6 +51,7 @@ const TaskCenterContainer = ({ visible, onClose, bodyshop, currentUser, setTaskU const { data: noDueDateData, loading: noDueLoading, + error: noDueError, fetchMore } = useQuery(QUERY_TASKS_NO_DUE_DATE_PAGINATED, { variables: { @@ -68,6 +73,10 @@ const TaskCenterContainer = ({ visible, onClose, bodyshop, currentUser, setTaskU setTasks([...dueDateTasks, ...noDueDateTasks]); }, [dueDateData, noDueDateData]); + const noDueDateLength = noDueDateData?.tasks?.length || 0; + const totalNoDueDate = noDueDateData?.tasks_aggregate?.aggregate?.count || 0; + const hasMore = noDueDateLength < totalNoDueDate; + // Handle pagination for no-due-date tasks const handleLoadMore = () => { fetchMore({ @@ -109,9 +118,10 @@ const TaskCenterContainer = ({ visible, onClose, bodyshop, currentUser, setTaskU onClose={onClose} tasks={tasks} loading={dueLoading || noDueLoading} + error={dueError || noDueError} onTaskClick={handleTaskClick} onLoadMore={handleLoadMore} - totalTasks={noDueDateData?.tasks_aggregate?.aggregate?.count || 0} + hasMore={hasMore} createNewTask={createNewTask} /> ); diff --git a/client/src/components/task-center/task-center.styles.scss b/client/src/components/task-center/task-center.styles.scss index b033ebdef..062aa133d 100644 --- a/client/src/components/task-center/task-center.styles.scss +++ b/client/src/components/task-center/task-center.styles.scss @@ -66,53 +66,50 @@ margin-bottom: 15px; } - .task-table { - width: 100%; + .task-row { + cursor: pointer; + border-bottom: 1px solid #f0f0f0; + display: flex; + justify-content: space-between; + align-items: flex-start; - border-collapse: collapse; + &:hover { + background: #f5f5f5; + } - .task-row { - cursor: pointer; - border-bottom: 1px solid #f0f0f0; + .task-title-cell { + flex: 1; + padding: 6px 8px; + vertical-align: top; + //font-size: 12px; + line-height: 1.2; + max-width: 350px; // or whatever fits your layout - &:hover { - background: #f5f5f5; - } - - td { - padding: 6px 8px; - vertical-align: top; - //font-size: 12px; - line-height: 1.2; - } - - .task-title-cell { - width: 100%; - max-width: 350px; // or whatever fits your layout - - .task-title { - font-size: 16px; - font-weight: 550; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 100%; // Or a specific width if you want more control - display: inline-block; - vertical-align: middle; - - } - - .task-ro-number { - margin-top: 20px; - color: #1677ff; - } - } - - .task-due-cell { - text-align: right; + .task-title { + font-size: 16px; + font-weight: 550; white-space: nowrap; - color: rgba(0, 0, 0, 0.45); + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; // Or a specific width if you want more control + display: inline-block; + vertical-align: middle; } + + .task-ro-number { + margin-top: 20px; + color: #1677ff; + } + } + + .task-due-cell { + padding: 6px 8px; + vertical-align: top; + //font-size: 12px; + line-height: 1.2; + text-align: right; + white-space: nowrap; + color: rgba(0, 0, 0, 0.45); } } @@ -136,8 +133,15 @@ } } - .ReactVirtuoso__item { - margin: 0 !important; - padding: 0 !important; + .no-tasks-message, + .error-message { + padding: 16px; + text-align: center; + color: rgba(0, 0, 0, 0.45); + } + + .loading-footer { + padding: 16px; + text-align: center; } }