From 4a30a5bc64780ce0739327a726646d7edb44d2e7 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 9 Jul 2025 17:47:32 -0400 Subject: [PATCH] feature/IO-3291-Tasks-Notifications: Checkpoint --- .../task-center/task-center.component.jsx | 46 +++--- .../task-center/task-center.container.jsx | 49 ++++-- .../task-center/task-center.styles.scss | 4 +- client/src/graphql/tasks.queries.js | 141 ++++++++++++------ 4 files changed, 156 insertions(+), 84 deletions(-) diff --git a/client/src/components/task-center/task-center.component.jsx b/client/src/components/task-center/task-center.component.jsx index e1e39771b..b2466adda 100644 --- a/client/src/components/task-center/task-center.component.jsx +++ b/client/src/components/task-center/task-center.component.jsx @@ -1,5 +1,5 @@ import { Virtuoso } from "react-virtuoso"; -import { Badge, Button, Spin, Tooltip, Typography } from "antd"; +import { Badge, Button, Spin } from "antd"; import { useTranslation } from "react-i18next"; import { forwardRef, useMemo, useRef } from "react"; import day from "../../utils/day.js"; @@ -12,8 +12,6 @@ import { QuestionCircleOutlined } from "@ant-design/icons"; -const { Title } = Typography; - const TaskCenterComponent = forwardRef( ({ visible, tasks, loading, onTaskClick, onLoadMore, totalTasks, createNewTask }, ref) => { const { t } = useTranslation(); @@ -25,13 +23,16 @@ const TaskCenterComponent = forwardRef( [t("tasks.labels.upcoming")]: , [t("tasks.labels.no_due_date")]: }; + const groupedItems = useMemo(() => { - const now = day(); + const now = day("2025-07-09"); // Set to current date const today = now.startOf("day"); const overdue = tasks.filter((t) => t.due_date && day(t.due_date).isBefore(today)); const dueToday = tasks.filter((t) => t.due_date && day(t.due_date).isSame(today, "day")); - const upcoming = tasks.filter((t) => t.due_date && day(t.due_date).isAfter(today)); + const upcoming = tasks.filter( + (t) => t.due_date && day(t.due_date).isAfter(today) && !day(t.due_date).isSame(today, "day") + ); const noDueDate = tasks.filter((t) => !t.due_date); const makeGroup = (label, data) => (data.length ? [{ type: "section", label, tasks: data }] : []); @@ -61,38 +62,30 @@ const TaskCenterComponent = forwardRef(
{sectionIcons[section.label]} - {section.label} + {section.label} ({section.tasks.length})
{section.tasks.map((task) => { const priorityColor = getPriorityColor(task.priority); - const rowContent = ( + return ( onTaskClick(task.id)}> -
-
- - + + + ); - - return task.description ? ( - - {rowContent} - - ) : ( - rowContent - ); })}
+ +
{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 && } - + {task.due_date && {day(task.due_date).fromNow()}} + {!!priorityColor && } +
@@ -102,17 +95,18 @@ const TaskCenterComponent = forwardRef( return (
-

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

+

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

renderSection(section, index)} /> diff --git a/client/src/components/task-center/task-center.container.jsx b/client/src/components/task-center/task-center.container.jsx index 4237aab34..66681fdce 100644 --- a/client/src/components/task-center/task-center.container.jsx +++ b/client/src/components/task-center/task-center.container.jsx @@ -7,9 +7,10 @@ import { useSocket } from "../../contexts/SocketIO/useSocket"; import { useIsEmployee } from "../../utils/useIsEmployee"; import TaskCenterComponent from "./task-center.component"; import { setModalContext } from "../../redux/modals/modals.actions"; -import { QUERY_MY_ACTIVE_TASKS_PAGINATED } from "../../graphql/tasks.queries"; +import { QUERY_TASKS_NO_DUE_DATE_PAGINATED, QUERY_TASKS_WITH_DUE_DATES } from "../../graphql/tasks.queries"; const POLL_INTERVAL = 60 * 1000; // milliseconds +const LIMIT = 50; // Tasks per page for no-due-date tasks const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -30,35 +31,55 @@ const TaskCenterContainer = ({ visible, onClose, bodyshop, currentUser, setTaskU return employee?.id || null; }, [bodyshop, currentUser]); - const { data, loading, fetchMore } = useQuery(QUERY_MY_ACTIVE_TASKS_PAGINATED, { + // Query 1: Tasks with due dates + const { data: dueDateData, loading: dueLoading } = useQuery(QUERY_TASKS_WITH_DUE_DATES, { variables: { bodyshop: bodyshop?.id, assigned_to: assignedToId, - offset: 0, - limit: 50, - order: [{ due_date: "asc_nulls_last" }, { created_at: "desc" }] + order: [{ due_date: "asc" }, { created_at: "desc" }] }, skip: !bodyshop?.id || !assignedToId || !isEmployee || !currentUser?.email, fetchPolicy: "cache-and-network", pollInterval: isConnected ? 0 : POLL_INTERVAL }); - useEffect(() => { - if (data?.tasks) { - setTasks(data.tasks); - } - }, [data]); + // Query 2: Tasks with no due date (paginated) + const { + data: noDueDateData, + loading: noDueLoading, + fetchMore + } = useQuery(QUERY_TASKS_NO_DUE_DATE_PAGINATED, { + variables: { + bodyshop: bodyshop?.id, + assigned_to: assignedToId, + order: [{ created_at: "desc" }], + limit: LIMIT, + offset: 0 + }, + skip: !bodyshop?.id || !assignedToId || !isEmployee || !currentUser?.email, + fetchPolicy: "cache-and-network", + pollInterval: isConnected ? 0 : POLL_INTERVAL + }); + // Combine tasks from both queries + useEffect(() => { + const dueDateTasks = dueDateData?.tasks || []; + const noDueDateTasks = noDueDateData?.tasks || []; + setTasks([...dueDateTasks, ...noDueDateTasks]); + }, [dueDateData, noDueDateData]); + + // Handle pagination for no-due-date tasks const handleLoadMore = () => { fetchMore({ variables: { - offset: tasks.length + offset: noDueDateData?.tasks?.length || 0 }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) return prev; return { ...prev, - tasks: [...prev.tasks, ...fetchMoreResult.tasks] + tasks: [...prev.tasks, ...fetchMoreResult.tasks], + tasks_aggregate: fetchMoreResult.tasks_aggregate }; } }); @@ -87,10 +108,10 @@ const TaskCenterContainer = ({ visible, onClose, bodyshop, currentUser, setTaskU visible={visible} onClose={onClose} tasks={tasks} - loading={loading} + loading={dueLoading || noDueLoading} onTaskClick={handleTaskClick} onLoadMore={handleLoadMore} - totalTasks={data?.tasks_aggregate?.aggregate?.count || 0} + totalTasks={noDueDateData?.tasks_aggregate?.aggregate?.count || 0} 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 b19f7148e..b033ebdef 100644 --- a/client/src/components/task-center/task-center.styles.scss +++ b/client/src/components/task-center/task-center.styles.scss @@ -50,8 +50,8 @@ } .section-title { - padding: 3px 10px; - margin: 0; + padding: 0px 10px; + margin: 0px; //font-size: 12px; background: #f5f5f5; font-weight: 650; diff --git a/client/src/graphql/tasks.queries.js b/client/src/graphql/tasks.queries.js index 5745e8045..c9496fcea 100644 --- a/client/src/graphql/tasks.queries.js +++ b/client/src/graphql/tasks.queries.js @@ -67,6 +67,105 @@ export const PARTIAL_TASK_FIELDS = gql` } `; +export const PARTIAL_TASK_CENTER_FIELDS = gql` + fragment TaskFields on tasks { + id + title + description + due_date + priority + jobid + job { + ro_number + } + joblineid + partsorderid + billid + remind_at + created_at + assigned_to + bodyshopid + deleted + completed + } +`; + +export const QUERY_TASKS_WITH_DUE_DATES = gql` + ${PARTIAL_TASK_CENTER_FIELDS} + query QUERY_TASKS_WITH_DUE_DATES($bodyshop: uuid!, $assigned_to: uuid!, $order: [tasks_order_by!]!) { + tasks( + where: { + bodyshopid: { _eq: $bodyshop } + assigned_to: { _eq: $assigned_to } + deleted: { _eq: false } + completed: { _eq: false } + due_date: { _is_null: false } + } + order_by: $order + ) { + ...TaskFields + } + } +`; +export const QUERY_TASKS_NO_DUE_DATE_PAGINATED = gql` + ${PARTIAL_TASK_CENTER_FIELDS} + query QUERY_TASKS_NO_DUE_DATE_PAGINATED( + $bodyshop: uuid! + $assigned_to: uuid! + $order: [tasks_order_by!]! + $limit: Int! + $offset: Int! + ) { + tasks( + where: { + bodyshopid: { _eq: $bodyshop } + assigned_to: { _eq: $assigned_to } + deleted: { _eq: false } + completed: { _eq: false } + due_date: { _is_null: true } + } + order_by: $order + limit: $limit + offset: $offset + ) { + ...TaskFields + } + tasks_aggregate( + where: { + bodyshopid: { _eq: $bodyshop } + assigned_to: { _eq: $assigned_to } + deleted: { _eq: false } + completed: { _eq: false } + due_date: { _is_null: true } + } + ) { + aggregate { + count + } + } + } +`; +/** + * Query to get the count of my tasks + * @type {DocumentNode} + */ +export const QUERY_MY_TASKS_COUNT = gql` + query QUERY_MY_TASKS_COUNT($assigned_to: uuid!, $bodyshopid: uuid!) { + tasks_aggregate( + where: { + assigned_to: { _eq: $assigned_to } + bodyshopid: { _eq: $bodyshopid } + completed: { _eq: false } + deleted: { _eq: false } + } + ) { + aggregate { + count + } + } + } +`; + export const QUERY_GET_TASK_BY_ID = gql` ${PARTIAL_TASK_FIELDS} query QUERY_GET_TASK_BY_ID($id: uuid!) { @@ -287,27 +386,6 @@ export const QUERY_JOB_TASKS_PAGINATED = gql` } `; -export const QUERY_MY_COMPLETE_TASKS_PAGINATED = gql` - ${PARTIAL_TASK_FIELDS} - query QUERY_MY_TASKS_PAGINATED( - $offset: Int - $limit: Int - $assigned_to: uuid! - $bodyshop: uuid! - $where: tasks_bool_exp - $order: [tasks_order_by!]! - ) { - tasks(offset: $offset, limit: $limit, order_by: $order, where: $where) { - ...TaskFields - } - tasks_aggregate(where: $where) { - aggregate { - count - } - } - } -`; - export const QUERY_MY_ACTIVE_TASKS_PAGINATED = gql` ${PARTIAL_TASK_FIELDS} query QUERY_MY_ACTIVE_TASKS_PAGINATED( @@ -439,24 +517,3 @@ export const MUTATION_UPDATE_TASK = gql` } } `; - -/** - * Query to get the count of my tasks - * @type {DocumentNode} - */ -export const QUERY_MY_TASKS_COUNT = gql` - query QUERY_MY_TASKS_COUNT($assigned_to: uuid!, $bodyshopid: uuid!) { - tasks_aggregate( - where: { - assigned_to: { _eq: $assigned_to } - bodyshopid: { _eq: $bodyshopid } - completed: { _eq: false } - deleted: { _eq: false } - } - ) { - aggregate { - count - } - } - } -`;