From 38aef71269b0347072156a2650133fb53115770a Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 20 Mar 2024 15:11:08 -0400 Subject: [PATCH 01/75] Progress --- .../components/header/header.component.jsx | 17 ++- .../task-list/task-list.component.jsx | 133 ++++++++++++++++++ .../task-list/task-list.container.jsx | 44 ++++++ client/src/graphql/bodyshop.queries.js | 19 +++ client/src/graphql/tasks.queries.js | 39 +++++ .../pages/manage/manage.page.component.jsx | 38 +++-- .../src/pages/tasks/tasks.page.component.jsx | 11 ++ .../src/pages/tasks/tasks.page.container.jsx | 47 +++++++ 8 files changed, 333 insertions(+), 15 deletions(-) create mode 100644 client/src/components/task-list/task-list.component.jsx create mode 100644 client/src/components/task-list/task-list.container.jsx create mode 100644 client/src/graphql/tasks.queries.js create mode 100644 client/src/pages/tasks/tasks.page.component.jsx create mode 100644 client/src/pages/tasks/tasks.page.container.jsx diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 20dae0de5..0dfac2ac6 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -30,7 +30,13 @@ import { Layout, Menu, Switch, Tooltip } from 'antd'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { BsKanban } from 'react-icons/bs'; -import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar } from 'react-icons/fa'; +import { + FaCalendarAlt, + FaCarCrash, + FaCreditCard, + FaFileInvoiceDollar, + FaTasks +} from 'react-icons/fa'; import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from 'react-icons/gi'; import { IoBusinessOutline } from 'react-icons/io5'; import { RiSurveyLine } from 'react-icons/ri'; @@ -329,9 +335,6 @@ function Header({ icon: , label: {t('menus.header.newjob')}, }, - { - type: 'divider', - }, { key: 'alljobs', icon: , @@ -463,6 +466,12 @@ function Header({ }, ] : []), + { + key: 'tasks', + id: 'tasks', + icon: , + label: {t('menus.header.tasks')}, + }, { key: 'shopsubmenu', icon: , diff --git a/client/src/components/task-list/task-list.component.jsx b/client/src/components/task-list/task-list.component.jsx new file mode 100644 index 000000000..bb56539a9 --- /dev/null +++ b/client/src/components/task-list/task-list.component.jsx @@ -0,0 +1,133 @@ +import {SyncOutlined} from "@ant-design/icons"; +import {Button, Card, Input, Space, Table, Typography} from "antd"; +import queryString from "query-string"; +import React, {useState} from "react"; +import {useTranslation} from "react-i18next"; +import {useLocation, useNavigate} from "react-router-dom"; +import {pageLimit} from "../../utils/config"; + +export default function TaskListComponent({ + loading, + tasks, + total, + refetch, + }) { + const search = queryString.parse(useLocation().search); + const { + page, + // sortcolumn, sortorder + } = search; + const history = useNavigate(); + + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {text: ""}, + }); + + const {t} = useTranslation(); + + const columns = [ + { + title: t("tasks.fields.title"), + dataIndex: "title", + key: "title", + }, + { + title: t("tasks.fields.description"), + dataIndex: "description", + key: "description", + }, + { + title: t("tasks.fields.due_date"), + dataIndex: "due_date", + key: "due_date", + }, + { + title: t("tasks.fields.assigned_to"), + dataIndex: "assigned_to", + key: "assigned_to", + }, + { + title: t("tasks.fields.completed"), + dataIndex: "completed", + key: "completed", + }, + { + title: t("tasks.fields.completed_at"), + dataIndex: "completed_at", + key: "completed_at", + }, + { + title: t("tasks.fields.remind_at"), + dataIndex: "remind_at", + key: "remind_at", + }, + { + title: t("tasks.fields.priority"), + dataIndex: "priority", + key: "priority", + }, + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({...state, filteredInfo: filters, sortedInfo: sorter}); + search.page = pagination.current; + search.sortcolumn = sorter.columnKey; + search.sortorder = sorter.order; + history({search: queryString.stringify(search)}); + }; + return ( + + {search.search && ( + <> + + {t("general.labels.searchresults", {search: search.search})} + + + + )} + + { + if (value?.length >= 3) { + search.search = value; + } else { + delete search.search; + } + history({search: queryString.stringify(search)}); + }} + enterButton + /> + + } + > + + + ); +} diff --git a/client/src/components/task-list/task-list.container.jsx b/client/src/components/task-list/task-list.container.jsx new file mode 100644 index 000000000..3d188694d --- /dev/null +++ b/client/src/components/task-list/task-list.container.jsx @@ -0,0 +1,44 @@ +import queryString from "query-string"; +import {useLocation} from "react-router-dom"; +import {useQuery} from "@apollo/client"; +import {QUERY_ALL_TASKS_PAGINATED} from "../../graphql/tasks.queries.js"; +import {pageLimit} from "../../utils/config.js"; +import AlertComponent from "../alert/alert.component.jsx"; +import React from "react"; +import TaskListComponent from "./task-list.component.jsx"; + +export default function TaskListContainer() { + const searchParams = queryString.parse(useLocation().search); + const {page, sortcolumn, sortorder, search} = searchParams; + const {loading, error, data, refetch} = useQuery( + QUERY_ALL_TASKS_PAGINATED, + { + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + variables: { + offset: page ? (page - 1) * pageLimit : 0, + limit: pageLimit, + order: [ + { + [sortcolumn || "created_at"]: sortorder + ? sortorder === "descend" + ? "desc" + : "asc" + : "desc", + }, + ], + }, + } + ); + + if (error) return ; + + return ( + + ); +} diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js index a8efb8d0b..5de9d7521 100644 --- a/client/src/graphql/bodyshop.queries.js +++ b/client/src/graphql/bodyshop.queries.js @@ -160,6 +160,25 @@ export const QUERY_BODYSHOP = gql` external_id flat_rate } + tasks { + id + created_at + updated_at + title + description + deleted + deleted_at + due_date + created_by + assigned_to + completed + completed_at + remind_at + priority + jobid + joblineid + partsorderid + } } } `; diff --git a/client/src/graphql/tasks.queries.js b/client/src/graphql/tasks.queries.js new file mode 100644 index 000000000..5ddaef3c1 --- /dev/null +++ b/client/src/graphql/tasks.queries.js @@ -0,0 +1,39 @@ +import {gql} from "@apollo/client"; + +export const QUERY_ALL_TASKS_PAGINATED = gql` + query QUERY_ALL_TASKS_PAGINATED( + $offset: Int + $limit: Int + $order: [tasks_order_by!]! + ) { + tasks( + offset: $offset + limit: $limit + order_by: $order + ) { + id + created_at + updated_at + title + description + deleted + deleted_at + due_date + created_by + assigned_to + completed + completed_at + remind_at + priority + jobid + joblineid + partsorderid + billid + } + tasks_aggregate(args: {}) { + aggregate { + count(distinct: true) + } + } + } +`; diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index 3dd482974..e29f462ff 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -1,10 +1,10 @@ -import { FloatButton, Layout, Spin } from "antd"; +import {FloatButton, Layout, Spin} from "antd"; // import preval from "preval.macro"; -import React, { lazy, Suspense, useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { connect } from "react-redux"; -import { Link, Route, Routes } from "react-router-dom"; -import { createStructuredSelector } from "reselect"; +import React, {lazy, Suspense, useEffect, useState} from "react"; +import {useTranslation} from "react-i18next"; +import {connect} from "react-redux"; +import {Link, Route, Routes} from "react-router-dom"; +import {createStructuredSelector} from "reselect"; import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component"; import ChatAffixContainer from "../../components/chat-affix/chat-affix.container"; import ConflictComponent from "../../components/conflict/conflict.component"; @@ -17,14 +17,18 @@ import TestComponent from "../../components/_test/test.page"; import HeaderContainer from "../../components/header/header.container"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import PartnerPingComponent from "../../components/partner-ping/partner-ping.component"; -import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; +import PrintCenterModalContainer + from "../../components/print-center-modal/print-center-modal.container"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component"; -import { requestForToken } from "../../firebase/firebase.utils"; -import { selectBodyshop, selectInstanceConflict, } from "../../redux/user/user.selectors"; +import {requestForToken} from "../../firebase/firebase.utils"; +import {selectBodyshop, selectInstanceConflict,} from "../../redux/user/user.selectors"; import UpdateAlert from "../../components/update-alert/update-alert.component"; -import { setJoyRideFinished } from "../../redux/application/application.actions.js"; -import { selectEnableJoyRide, selectJoyRideSteps } from "../../redux/application/application.selectors.js"; +import {setJoyRideFinished} from "../../redux/application/application.actions.js"; +import { + selectEnableJoyRide, + selectJoyRideSteps +} from "../../redux/application/application.selectors.js"; import InstanceRenderManager from "../../utils/instanceRenderMgr.js"; import "./manage.page.styles.scss"; @@ -180,6 +184,10 @@ const TtApprovals = lazy(() => import("../tt-approvals/tt-approvals.page.container") ); +const TasksPage = lazy(() => + import("../tasks/tasks.page.container") +); + const {Content, Footer} = Layout; @@ -286,7 +294,15 @@ export function Manage({conflict, bodyshop,enableJoyRide,joyRideSteps,setJoyRide }> }/> + {/*Start Task Routes */} }> + + } + /> + {/*End Task Routes */} + }> diff --git a/client/src/pages/tasks/tasks.page.component.jsx b/client/src/pages/tasks/tasks.page.component.jsx new file mode 100644 index 000000000..d1b80c856 --- /dev/null +++ b/client/src/pages/tasks/tasks.page.component.jsx @@ -0,0 +1,11 @@ +import React from "react"; +import TaskListContainer from "../../components/task-list/task-list.container.jsx"; + +export default function TasksPageComponent({bodyshop, currentUser}) { + + return ( +
+ +
+ ); +} diff --git a/client/src/pages/tasks/tasks.page.container.jsx b/client/src/pages/tasks/tasks.page.container.jsx new file mode 100644 index 000000000..c4151f1c6 --- /dev/null +++ b/client/src/pages/tasks/tasks.page.container.jsx @@ -0,0 +1,47 @@ +import React, {useEffect} from "react"; +import {useTranslation} from "react-i18next"; +import TasksPageComponent from "./tasks.page.component"; + +import {connect} from "react-redux"; +import {createStructuredSelector} from "reselect"; +import {setBreadcrumbs, setSelectedHeader} from "../../redux/application/application.actions"; +import InstanceRenderManager from "../../utils/instanceRenderMgr.js"; +import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors.js"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, + currentUser: selectCurrentUser, +}); + +const mapDispatchToProps = (dispatch) => ({ + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), +}); + +export function TasksPageContainer({bodyshop, currentUser, setBreadcrumbs, setSelectedHeader}) { + const {t} = useTranslation(); + useEffect(() => { + document.title = t("titles.tasks", { + app: InstanceRenderManager({ + imex: '$t(titles.imexonline)', + rome: '$t(titles.romeonline)', + promanager: '$t(titles.promanager)' + }) + }); + setSelectedHeader("tasks"); + setBreadcrumbs([ + { + link: "/manage/tasks", + label: t("titles.bc.tasks"), + },]); + }, [t, setBreadcrumbs, setSelectedHeader]); + + return ( + + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(TasksPageContainer); From f31ae9ac6d8d2a86d51d97d816770e8c92a52a24 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 20 Mar 2024 18:18:24 -0400 Subject: [PATCH 02/75] - Progress Commit Signed-off-by: Dave Richer --- .../task-list/task-list.component.jsx | 198 +++++++++++----- .../task-list/task-list.container.jsx | 47 +++- client/src/graphql/tasks.queries.js | 96 +++++++- .../src/pages/tasks/tasks.page.component.jsx | 2 +- hasura/metadata/actions.yaml | 10 +- hasura/metadata/allow_list.yaml | 2 +- hasura/metadata/cron_triggers.yaml | 2 +- hasura/metadata/network.yaml | 2 +- hasura/metadata/query_collections.yaml | 2 +- hasura/metadata/remote_schemas.yaml | 2 +- hasura/metadata/tables.yaml | 223 ++++++++++++++++-- 11 files changed, 488 insertions(+), 98 deletions(-) diff --git a/client/src/components/task-list/task-list.component.jsx b/client/src/components/task-list/task-list.component.jsx index bb56539a9..4a9aea817 100644 --- a/client/src/components/task-list/task-list.component.jsx +++ b/client/src/components/task-list/task-list.component.jsx @@ -1,74 +1,136 @@ -import {SyncOutlined} from "@ant-design/icons"; -import {Button, Card, Input, Space, Table, Typography} from "antd"; +import {Button, Card, Space, Switch, Table} from "antd"; import queryString from "query-string"; -import React, {useState} from "react"; +import React, {useCallback, useState} from "react"; import {useTranslation} from "react-i18next"; import {useLocation, useNavigate} from "react-router-dom"; import {pageLimit} from "../../utils/config"; +import dayjs from "../../utils/day"; +import { + CheckCircleFilled, + CheckCircleOutlined, + DeleteFilled, + DeleteOutlined, ExclamationCircleFilled, + SyncOutlined +} from "@ant-design/icons"; + +const DueDateRecord = ({dueDate}) => { + if (dueDate) { + const dueDateFormatted = dayjs(dueDate).format("YYYY-MM-DD"); + const relativeDueDate = dayjs(dueDate).fromNow(); + const today = dayjs().format("YYYY-MM-DD"); + if (dueDateFormatted < today) { + return
{dueDateFormatted}
; + } else { + return
{dueDateFormatted}
; + } + } else { + return
N/A
; + } +} +const PriorityLabel = ({priority}) => { + switch(priority) { + case 1: + return
+ High +
; + case 2: + return
+ Medium +
; + case 3: + return
+ Low +
; + default: + return
+ None +
; + } +} export default function TaskListComponent({ loading, tasks, total, refetch, + toggleCompletedStatus, }) { + const {t} = useTranslation(); + const search = queryString.parse(useLocation().search); + + // Extract Query Params const { page, - // sortcolumn, sortorder + sortcolumn, + sortorder, + deleted, + completed } = search; + const history = useNavigate(); - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: {text: ""}, - }); - - const {t} = useTranslation(); - const columns = [ { title: t("tasks.fields.title"), dataIndex: "title", key: "title", + sorter: true, + sortOrder: sortcolumn === "title" && sortorder, }, { title: t("tasks.fields.description"), dataIndex: "description", key: "description", + sorter: true, + sortOrder: sortcolumn === "description" && sortorder, }, { title: t("tasks.fields.due_date"), dataIndex: "due_date", key: "due_date", - }, - { - title: t("tasks.fields.assigned_to"), - dataIndex: "assigned_to", - key: "assigned_to", - }, - { - title: t("tasks.fields.completed"), - dataIndex: "completed", - key: "completed", - }, - { - title: t("tasks.fields.completed_at"), - dataIndex: "completed_at", - key: "completed_at", - }, - { - title: t("tasks.fields.remind_at"), - dataIndex: "remind_at", - key: "remind_at", + sorter: true, + sortOrder: sortcolumn === "due_date" && sortorder, + width: '5%', + render: (text, record) => , }, { title: t("tasks.fields.priority"), dataIndex: "priority", key: "priority", + sorter: true, + sortOrder: sortcolumn === "priority" && sortorder, + width: '5%', + render: (text, record) => + }, + { + title: t("tasks.fields.actions"), + key: "toggleCompleted", + width: '5%', + render: (text, record) => ( + + + + + ), }, ]; + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {text: ""}, + }); + + // Columns definition remains the same + const handleTableChange = (pagination, filters, sorter) => { setState({...state, filteredInfo: filters, sortedInfo: sorter}); search.page = pagination.current; @@ -76,43 +138,49 @@ export default function TaskListComponent({ search.sortorder = sorter.order; history({search: queryString.stringify(search)}); }; + + const handleSwitchChange = (param, value) => { + if (value) { + search[param] = "true"; + } else { + delete search[param]; + } + history({search: queryString.stringify(search)}); + }; + + /** + * Extra actions for the tasks + * @type {Function} + */ + const tasksExtra = useCallback(() => { + return ( + + } + unCheckedChildren={} + title={t('tasks.titles.completed')} + checked={completed === "true"} + onChange={(value) => handleSwitchChange('completed', value)} + /> + } + unCheckedChildren={} + title={t('tasks.titles.deleted')} + checked={deleted === "true"} + onChange={(value) => handleSwitchChange('deleted', value)} + /> + + + ); + }, [refetch, deleted, completed]); + return ( - {search.search && ( - <> - - {t("general.labels.searchresults", {search: search.search})} - - - - )} - - { - if (value?.length >= 3) { - search.search = value; - } else { - delete search.search; - } - history({search: queryString.stringify(search)}); - }} - enterButton - /> - - } + extra={tasksExtra()} >
{ + const completed_at = !currentStatus ? new Date().toISOString() : null; + await toggleTaskCompleted({ + variables: { + id: id, + completed: !currentStatus, + completed_at: completed_at + } + }); + refetch(); + }; + + const [toggleTaskDeleted] = useMutation(MUTATION_TOGGLE_TASK_DELETED); + + const toggleDeletedStatus = async (id, currentStatus) => { + const deleted_at = !currentStatus ? new Date().toISOString() : null; + await toggleTaskDeleted({ + variables: { + id: id, + deleted: !currentStatus, + deleted_at: deleted_at + } + }); + refetch(); + }; if (error) return ; @@ -39,6 +74,8 @@ export default function TaskListContainer() { tasks={data ? data.tasks : null} total={data ? data.tasks_aggregate.aggregate.count : 0} refetch={refetch} + toggleCompletedStatus={toggleCompletedStatus} + toggleDeletedStatus={toggleDeletedStatus} /> ); } diff --git a/client/src/graphql/tasks.queries.js b/client/src/graphql/tasks.queries.js index 5ddaef3c1..0a4375b1d 100644 --- a/client/src/graphql/tasks.queries.js +++ b/client/src/graphql/tasks.queries.js @@ -1,5 +1,9 @@ import {gql} from "@apollo/client"; +/** + * All tasks paginated query + * @type {DocumentNode} + */ export const QUERY_ALL_TASKS_PAGINATED = gql` query QUERY_ALL_TASKS_PAGINATED( $offset: Int @@ -30,10 +34,98 @@ export const QUERY_ALL_TASKS_PAGINATED = gql` partsorderid billid } - tasks_aggregate(args: {}) { + tasks_aggregate { aggregate { - count(distinct: true) + count } } } `; + +/** + * My tasks paginated query + * @type {DocumentNode} + */ +export const QUERY_MY_TASKS_PAGINATED = gql` + query QUERY_TASKS_PAGINATED( + $offset: Int + $limit: Int + $user: String! + $bodyshop: uuid! + $deleted: Boolean + $completed: Boolean + $order: [tasks_order_by!]! + ) { + tasks( + offset: $offset + limit: $limit + order_by: $order + where: { + assigned_to: {_eq: $user}, + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + completed: {_eq: $completed} + } + ) { + id + created_at + updated_at + title + description + deleted + deleted_at + due_date + created_by + assigned_to + completed + completed_at + remind_at + priority + jobid + joblineid + partsorderid + billid + bodyshopid + } + tasks_aggregate( + where: { + assigned_to: {_eq: $user}, + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + completed: {_eq: $completed} + } + ) { + aggregate { + count + } + } + } +`; + +/** + * Toggle task completed mutation + * @type {DocumentNode} + */ +export const MUTATION_TOGGLE_TASK_COMPLETED = gql` + mutation MUTATION_TOGGLE_TASK_COMPLETED($id: uuid!, $completed: Boolean!, $completed_at: timestamptz) { + update_tasks_by_pk(pk_columns: {id: $id}, _set: {completed: $completed, completed_at: $completed_at}) { + id + completed + completed_at + } + } +`; + +/** + * Toggle task deleted mutation + * @type {DocumentNode} + */ +export const MUTATION_TOGGLE_TASK_DELETED = gql` + mutation MUTATION_TOGGLE_TASK_DELETED($id: uuid!, $deleted: Boolean!, $deleted_at: timestamptz) { + update_tasks_by_pk(pk_columns: {id: $id}, _set: {deleted: $deleted, deleted_at: $deleted_at}) { + id + deleted + deleted_at + } + } +`; diff --git a/client/src/pages/tasks/tasks.page.component.jsx b/client/src/pages/tasks/tasks.page.component.jsx index d1b80c856..253bc9c4c 100644 --- a/client/src/pages/tasks/tasks.page.component.jsx +++ b/client/src/pages/tasks/tasks.page.component.jsx @@ -5,7 +5,7 @@ export default function TasksPageComponent({bodyshop, currentUser}) { return (
- +
); } diff --git a/hasura/metadata/actions.yaml b/hasura/metadata/actions.yaml index f5639566d..1edb4c2ff 100644 --- a/hasura/metadata/actions.yaml +++ b/hasura/metadata/actions.yaml @@ -1,6 +1,6 @@ -actions: [ ] +actions: [] custom_types: - enums: [ ] - input_objects: [ ] - objects: [ ] - scalars: [ ] + enums: [] + input_objects: [] + objects: [] + scalars: [] diff --git a/hasura/metadata/allow_list.yaml b/hasura/metadata/allow_list.yaml index 1e3ec7217..fe51488c7 100644 --- a/hasura/metadata/allow_list.yaml +++ b/hasura/metadata/allow_list.yaml @@ -1 +1 @@ -[ ] +[] diff --git a/hasura/metadata/cron_triggers.yaml b/hasura/metadata/cron_triggers.yaml index 1e3ec7217..fe51488c7 100644 --- a/hasura/metadata/cron_triggers.yaml +++ b/hasura/metadata/cron_triggers.yaml @@ -1 +1 @@ -[ ] +[] diff --git a/hasura/metadata/network.yaml b/hasura/metadata/network.yaml index ffcd4415b..0967ef424 100644 --- a/hasura/metadata/network.yaml +++ b/hasura/metadata/network.yaml @@ -1 +1 @@ -{ } +{} diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml index 1e3ec7217..fe51488c7 100644 --- a/hasura/metadata/query_collections.yaml +++ b/hasura/metadata/query_collections.yaml @@ -1 +1 @@ -[ ] +[] diff --git a/hasura/metadata/remote_schemas.yaml b/hasura/metadata/remote_schemas.yaml index 1e3ec7217..fe51488c7 100644 --- a/hasura/metadata/remote_schemas.yaml +++ b/hasura/metadata/remote_schemas.yaml @@ -1 +1 @@ -[ ] +[] diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 9cc14dd54..38dff43fa 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -53,7 +53,7 @@ update_permissions: - role: user permission: - columns: [ ] + columns: [] filter: jobline: job: @@ -569,6 +569,13 @@ table: name: parts_orders schema: public + - name: tasks + using: + foreign_key_constraint_on: + column: billid + table: + name: tasks + schema: public insert_permissions: - role: user permission: @@ -698,7 +705,7 @@ value_from_env: EVENT_SECRET request_transform: method: POST - query_params: { } + query_params: {} template_engine: Kriti url: '{{$base_url}}/opensearch' version: 2 @@ -818,6 +825,13 @@ table: name: inventory schema: public + - name: ioevents + using: + foreign_key_constraint_on: + column: bodyshopid + table: + name: ioevents + schema: public - name: jobs using: foreign_key_constraint_on: @@ -846,6 +860,13 @@ table: name: phonebook schema: public + - name: tasks + using: + foreign_key_constraint_on: + column: bodyshopid + table: + name: tasks + schema: public - name: timetickets using: foreign_key_constraint_on: @@ -1653,7 +1674,7 @@ columns: - config - id - filter: { } + filter: {} limit: 1 - role: user permission: @@ -2491,7 +2512,7 @@ - effective_date - end_date - content - filter: { } + filter: {} - table: name: exportlog schema: public @@ -2675,6 +2696,13 @@ - table: name: ioevents schema: public + object_relationships: + - name: bodyshop + using: + foreign_key_constraint_on: bodyshopid + - name: user + using: + foreign_key_constraint_on: useremail - table: name: job_ar_schema schema: public @@ -2824,6 +2852,13 @@ table: name: parts_order_lines schema: public + - name: tasks + using: + foreign_key_constraint_on: + column: joblineid + table: + name: tasks + schema: public insert_permissions: - role: user permission: @@ -3311,6 +3346,13 @@ table: name: scoreboard schema: public + - name: tasks + using: + foreign_key_constraint_on: + column: jobid + table: + name: tasks + schema: public - name: timetickets using: foreign_key_constraint_on: @@ -4200,13 +4242,13 @@ interval_sec: 10 num_retries: 0 timeout_sec: 60 - webhook: https://worktest.home.irony.online + webhook_from_env: HASURA_API_URL headers: - name: event-secret value_from_env: EVENT_SECRET request_transform: method: POST - query_params: { } + query_params: {} template_engine: Kriti url: '{{$base_url}}/job/statustransition' version: 2 @@ -4220,7 +4262,7 @@ webhook_from_env: HASURA_API_URL request_transform: method: POST - query_params: { } + query_params: {} template_engine: Kriti url: '{{$base_url}}/record-handler/arms' version: 2 @@ -4243,7 +4285,7 @@ value_from_env: EVENT_SECRET request_transform: method: POST - query_params: { } + query_params: {} template_engine: Kriti url: '{{$base_url}}/opensearch' version: 2 @@ -4256,13 +4298,13 @@ columns: - key - value - filter: { } + filter: {} - role: user permission: columns: - key - value - filter: { } + filter: {} - table: name: messages schema: public @@ -4694,7 +4736,7 @@ value_from_env: EVENT_SECRET request_transform: method: POST - query_params: { } + query_params: {} template_engine: Kriti url: '{{$base_url}}/opensearch' version: 2 @@ -5008,6 +5050,13 @@ table: name: parts_order_lines schema: public + - name: tasks + using: + foreign_key_constraint_on: + column: partsorderid + table: + name: tasks + schema: public insert_permissions: - role: user permission: @@ -5302,7 +5351,7 @@ value_from_env: EVENT_SECRET request_transform: method: POST - query_params: { } + query_params: {} template_engine: Kriti url: '{{$base_url}}/opensearch' version: 2 @@ -5623,6 +5672,129 @@ _eq: X-Hasura-User-Id - active: _eq: true +- table: + name: tasks + schema: public + object_relationships: + - name: bill + using: + foreign_key_constraint_on: billid + - name: bodyshop + using: + foreign_key_constraint_on: bodyshopid + - name: job + using: + foreign_key_constraint_on: jobid + - name: jobline + using: + foreign_key_constraint_on: joblineid + - name: parts_order + using: + foreign_key_constraint_on: partsorderid + - name: user + using: + foreign_key_constraint_on: assigned_to + - name: userByCreatedBy + using: + foreign_key_constraint_on: created_by + insert_permissions: + - role: user + permission: + check: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + columns: + - completed + - deleted + - priority + - assigned_to + - created_by + - description + - title + - completed_at + - created_at + - deleted_at + - due_date + - remind_at + - updated_at + - billid + - bodyshopid + - id + - jobid + - joblineid + - partsorderid + select_permissions: + - role: user + permission: + columns: + - completed + - deleted + - priority + - assigned_to + - created_by + - description + - title + - completed_at + - created_at + - deleted_at + - due_date + - remind_at + - updated_at + - billid + - bodyshopid + - id + - jobid + - joblineid + - partsorderid + filter: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + allow_aggregations: true + update_permissions: + - role: user + permission: + columns: + - completed + - deleted + - priority + - assigned_to + - created_by + - description + - title + - completed_at + - created_at + - deleted_at + - due_date + - remind_at + - updated_at + - billid + - bodyshopid + - id + - jobid + - joblineid + - partsorderid + filter: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + check: null - table: name: timetickets schema: public @@ -5843,7 +6015,7 @@ - user: authid: _eq: X-Hasura-User-Id - check: { } + check: {} - table: name: tt_approval_queue schema: public @@ -6006,6 +6178,13 @@ table: name: exportlog schema: public + - name: ioevents + using: + foreign_key_constraint_on: + column: useremail + table: + name: ioevents + schema: public - name: messages using: foreign_key_constraint_on: @@ -6034,6 +6213,20 @@ table: name: parts_orders schema: public + - name: tasks + using: + foreign_key_constraint_on: + column: assigned_to + table: + name: tasks + schema: public + - name: tasksByCreatedBy + using: + foreign_key_constraint_on: + column: created_by + table: + name: tasks + schema: public - name: timetickets using: foreign_key_constraint_on: @@ -6051,7 +6244,7 @@ insert_permissions: - role: user permission: - check: { } + check: {} columns: - authid - email @@ -6253,7 +6446,7 @@ value_from_env: EVENT_SECRET request_transform: method: POST - query_params: { } + query_params: {} template_engine: Kriti url: '{{$base_url}}/opensearch' version: 2 From ab2323e5c1202c3c3a57090611c690f2cca471bb Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 20 Mar 2024 22:19:52 -0400 Subject: [PATCH 03/75] - Progress Commit Signed-off-by: Dave Richer --- .../components/header/header.component.jsx | 60 +++++++--- .../task-list/task-list.component.jsx | 92 ++++++++++++--- .../task-list/task-list.container.jsx | 49 +++++++- .../task-upsert-modal.component.jsx | 107 ++++++++++++++++++ .../task-upsert-modal.container.jsx | 105 +++++++++++++++++ client/src/graphql/bodyshop.queries.js | 19 ---- client/src/graphql/tasks.queries.js | 70 +++++++++++- .../pages/manage/manage.page.component.jsx | 8 +- client/src/redux/modals/modals.reducer.js | 1 + client/src/redux/modals/modals.selectors.js | 5 + client/src/redux/modals/modals.types.js | 2 +- 11 files changed, 454 insertions(+), 64 deletions(-) create mode 100644 client/src/components/task-upsert-modal/task-upsert-modal.component.jsx create mode 100644 client/src/components/task-upsert-modal/task-upsert-modal.container.jsx diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 0dfac2ac6..75a9e39c0 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -17,6 +17,7 @@ import Icon, { LineChartOutlined, PaperClipOutlined, PhoneOutlined, + PlusCircleOutlined, QuestionCircleFilled, ScheduleOutlined, SettingOutlined, @@ -25,11 +26,11 @@ import Icon, { UnorderedListOutlined, UserOutlined, } from '@ant-design/icons'; -import { useSplitTreatments } from '@splitsoftware/splitio-react'; -import { Layout, Menu, Switch, Tooltip } from 'antd'; -import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { BsKanban } from 'react-icons/bs'; +import {useSplitTreatments} from '@splitsoftware/splitio-react'; +import {Layout, Menu, Switch, Tooltip} from 'antd'; +import React, {useEffect, useState} from 'react'; +import {useTranslation} from 'react-i18next'; +import {BsKanban} from 'react-icons/bs'; import { FaCalendarAlt, FaCarCrash, @@ -37,23 +38,23 @@ import { FaFileInvoiceDollar, FaTasks } from 'react-icons/fa'; -import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from 'react-icons/gi'; -import { IoBusinessOutline } from 'react-icons/io5'; -import { RiSurveyLine } from 'react-icons/ri'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; -import { createStructuredSelector } from 'reselect'; +import {GiPayMoney, GiPlayerTime, GiSettingsKnobs} from 'react-icons/gi'; +import {IoBusinessOutline} from 'react-icons/io5'; +import {RiSurveyLine} from 'react-icons/ri'; +import {connect} from 'react-redux'; +import {Link} from 'react-router-dom'; +import {createStructuredSelector} from 'reselect'; import { selectRecentItems, selectSelectedHeader, } from '../../redux/application/application.selectors'; -import { setModalContext } from '../../redux/modals/modals.actions'; -import { signOutStart } from '../../redux/user/user.actions'; -import { selectBodyshop, selectCurrentUser } from '../../redux/user/user.selectors'; -import { FiLogOut } from 'react-icons/fi'; -import { checkBeta, handleBeta, setBeta } from '../../utils/betaHandler'; +import {setModalContext} from '../../redux/modals/modals.actions'; +import {signOutStart} from '../../redux/user/user.actions'; +import {selectBodyshop, selectCurrentUser} from '../../redux/user/user.selectors'; +import {FiLogOut} from 'react-icons/fi'; +import {checkBeta, handleBeta, setBeta} from '../../utils/betaHandler'; import InstanceRenderManager from '../../utils/instanceRenderMgr'; -import { HasFeatureAccess } from '../feature-wrapper/feature-wrapper.component'; +import {HasFeatureAccess} from '../feature-wrapper/feature-wrapper.component'; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, @@ -73,6 +74,10 @@ const mapDispatchToProps = (dispatch) => ({ signOutStart: () => dispatch(signOutStart()), setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: 'cardPayment' })), + setTaskUpsertContext: (context) => dispatch(setModalContext({ + context: context, + modal: 'taskUpsert' + })), }); function Header({ @@ -87,6 +92,7 @@ function Header({ setReportCenterContext, recentItems, setCardPaymentContext, + setTaskUpsertContext, }) { const { treatments: { ImEXPay, DmsAp, Simple_Inventory }, @@ -470,7 +476,25 @@ function Header({ key: 'tasks', id: 'tasks', icon: , - label: {t('menus.header.tasks')}, + label: t('menus.header.tasks'), + children: [ + { + key: 'createTask', + icon: , + label: t('menus.header.create_task'), + onClick: () => { + setTaskUpsertContext({ + actions: {}, + context: {}, + }); + }, + }, + { + key: 'mytasks', + icon: , + label: {t('menus.header.tasks')}, + } + ] }, { key: 'shopsubmenu', diff --git a/client/src/components/task-list/task-list.component.jsx b/client/src/components/task-list/task-list.component.jsx index 4a9aea817..2f1d125b8 100644 --- a/client/src/components/task-list/task-list.component.jsx +++ b/client/src/components/task-list/task-list.component.jsx @@ -2,58 +2,91 @@ import {Button, Card, Space, Switch, Table} from "antd"; import queryString from "query-string"; import React, {useCallback, useState} from "react"; import {useTranslation} from "react-i18next"; -import {useLocation, useNavigate} from "react-router-dom"; +import {Link, useLocation, useNavigate} from "react-router-dom"; import {pageLimit} from "../../utils/config"; import dayjs from "../../utils/day"; import { CheckCircleFilled, CheckCircleOutlined, DeleteFilled, - DeleteOutlined, ExclamationCircleFilled, + DeleteOutlined, + EditFilled, + ExclamationCircleFilled, + PlusCircleFilled, SyncOutlined } from "@ant-design/icons"; +import {DateFormatter} from "../../utils/DateFormatter.jsx"; +import {connect} from 'react-redux'; +import {setModalContext} from '../../redux/modals/modals.actions'; +/** + * Task List Component + * @param dueDate + * @returns {Element} + * @constructor + */ const DueDateRecord = ({dueDate}) => { if (dueDate) { - const dueDateFormatted = dayjs(dueDate).format("YYYY-MM-DD"); - const relativeDueDate = dayjs(dueDate).fromNow(); - const today = dayjs().format("YYYY-MM-DD"); - if (dueDateFormatted < today) { - return
{dueDateFormatted}
; + const dueDateDayjs = dayjs(dueDate); + const relativeDueDate = dueDateDayjs.fromNow(); + const today = dayjs(); + + if (dueDateDayjs.isAfter(today)) { + return
+ {dueDate}
; } else { - return
{dueDateFormatted}
; + return
{dueDate}
; } } else { return
N/A
; } } + +/** + * Priority Label Component + * @param priority + * @returns {Element} + * @constructor + */ const PriorityLabel = ({priority}) => { switch(priority) { case 1: return
- High + High
; case 2: return
- Medium + Medium
; case 3: return
- Low + Low
; default: return
- None + None
; } } -export default function TaskListComponent({ +const mapDispatchToProps = (dispatch) => ({ + // Existing dispatch props... + setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})), +}); + +const mapStateToProps = (state) => ({ + // Existing state props... +}); + +export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent); + +function TaskListComponent({ loading, tasks, total, refetch, toggleCompletedStatus, + setTaskUpsertContext, }) { const {t} = useTranslation(); @@ -71,6 +104,17 @@ export default function TaskListComponent({ const history = useNavigate(); const columns = [ + { + title: t("tasks.fields.job.ro_number"), + dataIndex: ["job", "ro_number"], + key: "job.ro_number", + width: '5%', + render: (text, record) => ( + + {record.job.ro_number} + + ), + }, { title: t("tasks.fields.title"), dataIndex: "title", @@ -110,6 +154,16 @@ export default function TaskListComponent({ width: '5%', render: (text, record) => ( +
{ + const handleTaskUpdated = () => { + refetch().catch((e) => { + console.error(`Something went wrong fetching tasks: ${e.message || ''}`); + }); }; + + window.addEventListener('taskUpdated', handleTaskUpdated); + window.addEventListener('taskCreated', handleTaskUpdated); + + return () => { + window.removeEventListener('taskUpdated', handleTaskUpdated); + window.removeEventListener('taskCreated', handleTaskUpdated); + }; + }, [refetch]); + + /** + * Toggle task completed mutation + */ const [toggleTaskCompleted] = useMutation(MUTATION_TOGGLE_TASK_COMPLETED); + /** + * Toggle task completed status + * @param id + * @param currentStatus + * @returns {Promise} + */ const toggleCompletedStatus = async (id, currentStatus) => { const completed_at = !currentStatus ? new Date().toISOString() : null; await toggleTaskCompleted({ @@ -49,11 +76,21 @@ export default function TaskListContainer({bodyshop, currentUser}) { completed_at: completed_at } }); - refetch(); - }; + refetch().catch((e) => { + console.error(`Something went wrong fetching tasks: ${e.message || ''}`); + }); }; + /** + * Toggle task deleted mutation + */ const [toggleTaskDeleted] = useMutation(MUTATION_TOGGLE_TASK_DELETED); + /** + * Toggle task deleted status + * @param id + * @param currentStatus + * @returns {Promise} + */ const toggleDeletedStatus = async (id, currentStatus) => { const deleted_at = !currentStatus ? new Date().toISOString() : null; await toggleTaskDeleted({ @@ -63,7 +100,9 @@ export default function TaskListContainer({bodyshop, currentUser}) { deleted_at: deleted_at } }); - refetch(); + refetch().catch((e) => { + console.error(`Something went wrong fetching tasks: ${e.message || ''}`); + }); }; if (error) return ; diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx new file mode 100644 index 000000000..b1661a6a0 --- /dev/null +++ b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx @@ -0,0 +1,107 @@ +import {Col, Form, Input, Row, Select, Switch} from "antd"; +import React from "react"; +import {useTranslation} from "react-i18next"; +import {FormDatePicker} from "../form-date-picker/form-date-picker.component.jsx"; +import {createStructuredSelector} from "reselect"; +import {selectBodyshop} from "../../redux/user/user.selectors.js"; +import {connect} from "react-redux"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +const mapDispatchToProps = (dispatch) => ({}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(TaskUpsertModalComponent); + + +export function TaskUpsertModalComponent({form, bodyshop}) { + const {t} = useTranslation(); + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx new file mode 100644 index 000000000..f8bfcbe28 --- /dev/null +++ b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx @@ -0,0 +1,105 @@ +import {useMutation} from "@apollo/client"; +import {Form, Modal, notification} from "antd"; +import React, {useEffect} from "react"; +import {useTranslation} from "react-i18next"; +import {connect} from "react-redux"; +import {createStructuredSelector} from "reselect"; +import {INSERT_NEW_TASK, UPDATE_TASK} from "../../graphql/tasks.queries"; +import {toggleModalVisible} from "../../redux/modals/modals.actions"; +import {selectTaskUpsert} from "../../redux/modals/modals.selectors"; +import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; +import TaskUpsertModalComponent from "./task-upsert-modal.component"; + +const mapStateToProps = createStructuredSelector({ + currentUser: selectCurrentUser, + bodyshop: selectBodyshop, + taskUpsert: selectTaskUpsert, +}); +const mapDispatchToProps = (dispatch) => ({ + toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")), +}); + +export function TaskUpsertModalContainer({ + bodyshop, + currentUser, + taskUpsert, + toggleModalVisible, + }) { + const {t} = useTranslation(); + const [insertTask] = useMutation(INSERT_NEW_TASK); + const [updateTask] = useMutation(UPDATE_TASK); + + const {open, context, actions} = taskUpsert; + const {jobId, existingTask} = context; + const {refetch} = actions; + + const [form] = Form.useForm(); + + useEffect(() => { + if (existingTask && open) { + form.setFieldsValue(existingTask); + } else if (!existingTask && open) { + form.resetFields(); + } + }, [existingTask, form, open]); + + const handleFinish = async (formValues) => { + const {...values} = formValues; + + if (existingTask) { + updateTask({ + variables: { + taskId: existingTask.id, + task: values, + }, + }).then((r) => { + window.dispatchEvent(new CustomEvent('taskUpdated')); + notification["success"]({ + message: t("tasks.successes.updated"), + }); + }); + if (refetch) refetch(); + toggleModalVisible(); + } else { + await insertTask({ + variables: { + taskInput: [ + {...values, jobid: jobId, created_by: currentUser.email, bodyshopid: bodyshop.id}, + ], + }, + }); + + if (refetch) refetch(); + form.resetFields(); + toggleModalVisible(); + window.dispatchEvent(new CustomEvent('taskUpdated')); + notification["success"]({ + message: t("tasks.successes.create"), + }); + } + }; + + return ( + { + form.submit(); + }} + onCancel={() => { + toggleModalVisible(); + }} + destroyOnClose + > +
+ + +
+ ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(TaskUpsertModalContainer); diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js index 5de9d7521..a8efb8d0b 100644 --- a/client/src/graphql/bodyshop.queries.js +++ b/client/src/graphql/bodyshop.queries.js @@ -160,25 +160,6 @@ export const QUERY_BODYSHOP = gql` external_id flat_rate } - tasks { - id - created_at - updated_at - title - description - deleted - deleted_at - due_date - created_by - assigned_to - completed - completed_at - remind_at - priority - jobid - joblineid - partsorderid - } } } `; diff --git a/client/src/graphql/tasks.queries.js b/client/src/graphql/tasks.queries.js index 0a4375b1d..c55987a83 100644 --- a/client/src/graphql/tasks.queries.js +++ b/client/src/graphql/tasks.queries.js @@ -81,10 +81,10 @@ export const QUERY_MY_TASKS_PAGINATED = gql` completed_at remind_at priority - jobid - joblineid - partsorderid - billid + job { + ro_number + id + } bodyshopid } tasks_aggregate( @@ -129,3 +129,65 @@ export const MUTATION_TOGGLE_TASK_DELETED = gql` } } `; + +/** + * Insert new task mutation + * @type {DocumentNode} + */ +export const INSERT_NEW_TASK = gql` + mutation INSERT_NEW_TASK($taskInput: [tasks_insert_input!]!) { + insert_tasks(objects: $taskInput) { + returning { + id + created_at + updated_at + title + description + deleted + deleted_at + due_date + created_by + assigned_to + completed + completed_at + remind_at + priority + jobid + joblineid + partsorderid + billid + } + } + } +`; + +/** + * Update task mutation + * @type {DocumentNode} + */ +export const UPDATE_TASK = gql` + mutation UPDATE_TASK($taskId: uuid!, $task: tasks_set_input!) { + update_tasks(where: { id: { _eq: $taskId } }, _set: $task) { + returning { + id + created_at + updated_at + title + description + deleted + deleted_at + due_date + created_by + assigned_to + completed + completed_at + remind_at + priority + jobid + joblineid + partsorderid + billid + } + } + } +` diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index e29f462ff..c7a717e36 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -35,7 +35,11 @@ import "./manage.page.styles.scss"; const JobsPage = lazy(() => import("../jobs/jobs.page")); const CardPaymentModalContainer = lazy(() => - import("../../components/card-payment-modal/card-payment-modal.container.") + import("../../components/card-payment-modal/card-payment-modal.container..jsx") +); + +const TaskUpsertModalContainer = lazy(() => + import("../../components/task-upsert-modal/task-upsert-modal.container.jsx") ); const JobsDetailPage = lazy(() => @@ -229,7 +233,7 @@ export function Manage({conflict, bodyshop,enableJoyRide,joyRideSteps,setJoyRide - + diff --git a/client/src/redux/modals/modals.reducer.js b/client/src/redux/modals/modals.reducer.js index 5cd7e5763..b7d8c2cee 100644 --- a/client/src/redux/modals/modals.reducer.js +++ b/client/src/redux/modals/modals.reducer.js @@ -13,6 +13,7 @@ const INITIAL_STATE = { billEnter: {...baseModal}, courtesyCarReturn: {...baseModal}, noteUpsert: {...baseModal}, + taskUpsert: {...baseModal}, schedule: {...baseModal}, partsOrder: {...baseModal}, timeTicket: {...baseModal}, diff --git a/client/src/redux/modals/modals.selectors.js b/client/src/redux/modals/modals.selectors.js index a2f7d5ade..6d2f383f7 100644 --- a/client/src/redux/modals/modals.selectors.js +++ b/client/src/redux/modals/modals.selectors.js @@ -22,6 +22,11 @@ export const selectNoteUpsert = createSelector( (modals) => modals.noteUpsert ); +export const selectTaskUpsert = createSelector( + [selectModals], + (modals) => modals.taskUpsert +); + export const selectSchedule = createSelector( [selectModals], (modals) => modals.schedule diff --git a/client/src/redux/modals/modals.types.js b/client/src/redux/modals/modals.types.js index 911f4c581..6bc9a7a9e 100644 --- a/client/src/redux/modals/modals.types.js +++ b/client/src/redux/modals/modals.types.js @@ -1,5 +1,5 @@ const ModalActionTypes = { TOGGLE_MODAL_VISIBLE: "TOGGLE_MODAL_VISIBLE", - SET_MODAL_CONTEXT: "SET_MODAL_CONTEXT" + SET_MODAL_CONTEXT: "SET_MODAL_CONTEXT", }; export default ModalActionTypes; From 9012e4deec513c848a134801cc5d63fe0d262b26 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Mar 2024 13:42:08 -0400 Subject: [PATCH 04/75] - Progress Commit Signed-off-by: Dave Richer --- .../components/header/header.component.jsx | 2 +- .../task-upsert-modal.container.jsx | 24 ++++++++++++++----- client/src/graphql/tasks.queries.js | 8 +++---- client/src/redux/modals/modals.reducer.js | 2 +- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 75a9e39c0..1b5d5b5c4 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -476,7 +476,7 @@ function Header({ key: 'tasks', id: 'tasks', icon: , - label: t('menus.header.tasks'), + label: t('menus.header.my_tasks'), children: [ { key: 'createTask', diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx index f8bfcbe28..d091b595c 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx @@ -4,7 +4,7 @@ import React, {useEffect} from "react"; import {useTranslation} from "react-i18next"; import {connect} from "react-redux"; import {createStructuredSelector} from "reselect"; -import {INSERT_NEW_TASK, UPDATE_TASK} from "../../graphql/tasks.queries"; +import {MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK} from "../../graphql/tasks.queries"; import {toggleModalVisible} from "../../redux/modals/modals.actions"; import {selectTaskUpsert} from "../../redux/modals/modals.selectors"; import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; @@ -26,13 +26,12 @@ export function TaskUpsertModalContainer({ toggleModalVisible, }) { const {t} = useTranslation(); - const [insertTask] = useMutation(INSERT_NEW_TASK); - const [updateTask] = useMutation(UPDATE_TASK); + const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK); + const [updateTask] = useMutation(MUTATION_UPDATE_TASK); const {open, context, actions} = taskUpsert; const {jobId, existingTask} = context; const {refetch} = actions; - const [form] = Form.useForm(); useEffect(() => { @@ -53,7 +52,6 @@ export function TaskUpsertModalContainer({ task: values, }, }).then((r) => { - window.dispatchEvent(new CustomEvent('taskUpdated')); notification["success"]({ message: t("tasks.successes.updated"), }); @@ -67,12 +65,26 @@ export function TaskUpsertModalContainer({ {...values, jobid: jobId, created_by: currentUser.email, bodyshopid: bodyshop.id}, ], }, + update(cache) { + cache.modify({ + + fields: { + tasks(existingTasks) { + return [{ + ...values, + jobid: jobId, + created_by: currentUser.email, + bodyshopid: bodyshop.id + }, ...existingTasks] + }, + }, + }); + }, }); if (refetch) refetch(); form.resetFields(); toggleModalVisible(); - window.dispatchEvent(new CustomEvent('taskUpdated')); notification["success"]({ message: t("tasks.successes.create"), }); diff --git a/client/src/graphql/tasks.queries.js b/client/src/graphql/tasks.queries.js index c55987a83..9a501f432 100644 --- a/client/src/graphql/tasks.queries.js +++ b/client/src/graphql/tasks.queries.js @@ -134,8 +134,8 @@ export const MUTATION_TOGGLE_TASK_DELETED = gql` * Insert new task mutation * @type {DocumentNode} */ -export const INSERT_NEW_TASK = gql` - mutation INSERT_NEW_TASK($taskInput: [tasks_insert_input!]!) { +export const MUTATION_INSERT_NEW_TASK = gql` + mutation MUTATION_INSERT_NEW_TASK($taskInput: [tasks_insert_input!]!) { insert_tasks(objects: $taskInput) { returning { id @@ -165,8 +165,8 @@ export const INSERT_NEW_TASK = gql` * Update task mutation * @type {DocumentNode} */ -export const UPDATE_TASK = gql` - mutation UPDATE_TASK($taskId: uuid!, $task: tasks_set_input!) { +export const MUTATION_UPDATE_TASK = gql` + mutation MUTATION_UPDATE_TASK($taskId: uuid!, $task: tasks_set_input!) { update_tasks(where: { id: { _eq: $taskId } }, _set: $task) { returning { id diff --git a/client/src/redux/modals/modals.reducer.js b/client/src/redux/modals/modals.reducer.js index b7d8c2cee..6260fe39e 100644 --- a/client/src/redux/modals/modals.reducer.js +++ b/client/src/redux/modals/modals.reducer.js @@ -13,7 +13,7 @@ const INITIAL_STATE = { billEnter: {...baseModal}, courtesyCarReturn: {...baseModal}, noteUpsert: {...baseModal}, - taskUpsert: {...baseModal}, + taskUpsert: {...baseModal, }, schedule: {...baseModal}, partsOrder: {...baseModal}, timeTicket: {...baseModal}, From 40c801592d1a853ce7a6d836457aaf0ffaa1d908 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 22 Mar 2024 12:20:56 -0400 Subject: [PATCH 05/75] - Progress Commit Signed-off-by: Dave Richer --- .../task-upsert-modal.component.jsx | 30 +++++++++++++++++++ client/src/graphql/tasks.queries.js | 25 ++++++++++++++-- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx index b1661a6a0..44262b68d 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx @@ -101,6 +101,36 @@ export function TaskUpsertModalComponent({form, bodyshop}) { + + + + + + + + + + + + + + + ); diff --git a/client/src/graphql/tasks.queries.js b/client/src/graphql/tasks.queries.js index 9a501f432..d19b70315 100644 --- a/client/src/graphql/tasks.queries.js +++ b/client/src/graphql/tasks.queries.js @@ -82,10 +82,31 @@ export const QUERY_MY_TASKS_PAGINATED = gql` remind_at priority job { - ro_number id + ro_number + joblines { + id + line_desc + } + bills { + id + vendor { + name + } + invoice_number + } + parts_orders { + id + vendor { + name + } + order_number + } } - bodyshopid + jobid + joblineid + partsorderid + billid } tasks_aggregate( where: { From 595159f24dd0b710ec59b04b955e4fb1e7c1e3c5 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 26 Mar 2024 19:59:30 -0400 Subject: [PATCH 06/75] - Progress commit Signed-off-by: Dave Richer --- .../task-list/task-list.container.jsx | 8 - .../task-upsert-modal.component.jsx | 143 ++++++++++++------ .../task-upsert-modal.container.jsx | 97 ++++++++++-- client/src/graphql/jobs.queries.js | 38 +++++ 4 files changed, 218 insertions(+), 68 deletions(-) diff --git a/client/src/components/task-list/task-list.container.jsx b/client/src/components/task-list/task-list.container.jsx index bd5095fdc..3efab337b 100644 --- a/client/src/components/task-list/task-list.container.jsx +++ b/client/src/components/task-list/task-list.container.jsx @@ -46,14 +46,6 @@ export default function TaskListContainer({bodyshop, currentUser}) { refetch().catch((e) => { console.error(`Something went wrong fetching tasks: ${e.message || ''}`); }); }; - - window.addEventListener('taskUpdated', handleTaskUpdated); - window.addEventListener('taskCreated', handleTaskUpdated); - - return () => { - window.removeEventListener('taskUpdated', handleTaskUpdated); - window.removeEventListener('taskCreated', handleTaskUpdated); - }; }, [refetch]); /** diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx index 44262b68d..05ac709f7 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx @@ -1,10 +1,12 @@ import {Col, Form, Input, Row, Select, Switch} from "antd"; -import React from "react"; +import React, {useCallback} from "react"; import {useTranslation} from "react-i18next"; import {FormDatePicker} from "../form-date-picker/form-date-picker.component.jsx"; import {createStructuredSelector} from "reselect"; import {selectBodyshop} from "../../redux/user/user.selectors.js"; + import {connect} from "react-redux"; +import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -18,12 +20,54 @@ export default connect( )(TaskUpsertModalComponent); -export function TaskUpsertModalComponent({form, bodyshop}) { +export function TaskUpsertModalComponent({ + form, + bodyshop, + selectedJobId, + setSelectedJobId, + selectedJobDetails, + loading, + error, + data + }) { const {t} = useTranslation(); + + console.dir(data) + + if (loading || error) return ; + return ( <> - + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + {selectedJobDetails?.joblines?.map((jobline) => ( + + {jobline.line_desc} + + ))} @@ -116,7 +165,7 @@ export function TaskUpsertModalComponent({form, bodyshop}) { label={t("tasks.fields.partsorderid")} name="partsorderid" > - {/* Add your options here */} @@ -126,7 +175,7 @@ export function TaskUpsertModalComponent({form, bodyshop}) { label={t("tasks.fields.billid")} name="billid" > - {/* Add your options here */} diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx index d091b595c..7a5e0ddf6 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx @@ -1,10 +1,14 @@ -import {useMutation} from "@apollo/client"; +import {useMutation, useQuery} from "@apollo/client"; import {Form, Modal, notification} from "antd"; -import React, {useEffect} from "react"; +import React, {useEffect, useState} from "react"; import {useTranslation} from "react-i18next"; import {connect} from "react-redux"; import {createStructuredSelector} from "reselect"; import {MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK} from "../../graphql/tasks.queries"; +import { + QUERY_GET_TASKS_JOB_DETAILS, + QUERY_GET_TASKS_JOB_DETAILS_BY_ID +} from "../../graphql/jobs.queries.js"; import {toggleModalVisible} from "../../redux/modals/modals.actions"; import {selectTaskUpsert} from "../../redux/modals/modals.selectors"; import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; @@ -33,7 +37,33 @@ export function TaskUpsertModalContainer({ const {jobId, existingTask} = context; const {refetch} = actions; const [form] = Form.useForm(); + const [selectedJobId, setSelectedJobId] = useState(null); + const [selectedJobDetails, setSelectedJobDetails] = useState(null); + const [jobIdState, setJobIdState] = useState(null); + + const { + loading: jobDetailsLoading, + error: jobDetailsError, + data: jobDetailsData + } = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, { + variables: {id: jobIdState}, + skip: !jobIdState, // Skip the query if jobIdState is null + }); + const {loading, error, data} = useQuery(QUERY_GET_TASKS_JOB_DETAILS); + + /** + * Set the selected job id when the modal is opened and jobId is passed as a prop or when an existing task is passed as a prop + */ + useEffect(() => { + if (jobId || existingTask?.id) { + setSelectedJobId(jobId || existingTask.jobid); + } + }, [jobId, existingTask]); + + /** + * Set the form values when the modal is opened and an existing task is passed as a prop + */ useEffect(() => { if (existingTask && open) { form.setFieldsValue(existingTask); @@ -42,37 +72,72 @@ export function TaskUpsertModalContainer({ } }, [existingTask, form, open]); + /** + * Reset the form values when the selected job id changes + */ + useEffect(() => { + form.setFieldsValue({ + joblineid: undefined, + billid: undefined, + partsorderid: undefined, + }); + }, [selectedJobId, form]); + + /** + * Set the job id state when the selected job id changes + */ + useEffect(() => { + if (selectedJobId) { + // Update the state variable instead of calling useQuery + setJobIdState(selectedJobId); + } + }, [selectedJobId]); + + /** + * Set the selected job details when the job details query is successful + */ + useEffect(() => { + if (!jobDetailsLoading && !jobDetailsError && jobDetailsData) { + setSelectedJobDetails(jobDetailsData.jobs_by_pk); + } + }, [jobDetailsLoading, jobDetailsError, jobDetailsData]); + + /** + * Handle the form submit + * @param formValues + * @returns {Promise<[{jobid, bodyshopid, created_by},...*]>} + */ const handleFinish = async (formValues) => { const {...values} = formValues; if (existingTask) { - updateTask({ + await updateTask({ variables: { taskId: existingTask.id, task: values, + jobid: selectedJobId, }, - }).then((r) => { - notification["success"]({ - message: t("tasks.successes.updated"), - }); }); - if (refetch) refetch(); + + notification["success"]({ + message: t("tasks.successes.updated"), + }); + if (refetch) await refetch(); toggleModalVisible(); } else { await insertTask({ variables: { taskInput: [ - {...values, jobid: jobId, created_by: currentUser.email, bodyshopid: bodyshop.id}, + {...values, jobid: selectedJobId, created_by: currentUser.email, bodyshopid: bodyshop.id}, ], }, update(cache) { cache.modify({ - fields: { tasks(existingTasks) { return [{ ...values, - jobid: jobId, + jobid: selectedJobId, created_by: currentUser.email, bodyshopid: bodyshop.id }, ...existingTasks] @@ -82,7 +147,7 @@ export function TaskUpsertModalContainer({ }, }); - if (refetch) refetch(); + if (refetch) await refetch(); form.resetFields(); toggleModalVisible(); notification["success"]({ @@ -96,16 +161,22 @@ export function TaskUpsertModalContainer({ title={existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")} open={open} okText={t("general.actions.save")} + width="50%" onOk={() => { form.submit(); }} onCancel={() => { toggleModalVisible(); }} + destroyOnClose > - + + ); diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 174f6ad04..a2a2e43eb 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -2130,6 +2130,44 @@ export const DELETE_JOB = gql` } `; +export const QUERY_GET_TASKS_JOB_DETAILS_BY_ID = gql` + query GetTasksJobDetailsById($id: uuid!) { + jobs_by_pk(id: $id) { + id + joblines { + id + line_desc + } + bills { + id + vendor { + name + } + invoice_number + } + parts_orders { + id + vendor { + name + } + order_number + } + } + } +`; + +export const QUERY_GET_TASKS_JOB_DETAILS = gql` + query GetTasksJobDetails { + jobs { + id + ro_number + ownr_fn + ownr_ln + ownr_co_nm + } + } +`; + export const GET_JOB_FOR_CC_CONTRACT = gql` query GET_JOB_FOR_CC_CONTRACT($id: uuid!) { jobs_by_pk(id: $id) { From 301c680bffe89f053d68510426e85eb618d78686 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 26 Mar 2024 21:36:27 -0400 Subject: [PATCH 07/75] - Progress commit Signed-off-by: Dave Richer --- .../components/header/header.component.jsx | 4 +- .../jobs-detail-header-actions.component.jsx | 2 +- .../task-list/task-list.component.jsx | 27 +++++---- .../task-upsert-modal.component.jsx | 59 ++++++++++++------- .../task-upsert-modal.container.jsx | 3 + client/src/translations/en_us/common.json | 55 +++++++++++++++++ client/src/translations/es/common.json | 55 +++++++++++++++++ client/src/translations/fr/common.json | 55 +++++++++++++++++ 8 files changed, 222 insertions(+), 38 deletions(-) diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 1b5d5b5c4..150298ee5 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -476,7 +476,7 @@ function Header({ key: 'tasks', id: 'tasks', icon: , - label: t('menus.header.my_tasks'), + label: t('menus.header.tasks'), children: [ { key: 'createTask', @@ -492,7 +492,7 @@ function Header({ { key: 'mytasks', icon: , - label: {t('menus.header.tasks')}, + label: {t('menus.header.my_tasks')}, } ] }, diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx index 27c81d7b2..0a5ac3ae0 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx @@ -774,7 +774,7 @@ export function JobsDetailHeaderActions({ menuItems.push( job.inproduction ? { - key: 'addtoproduction', + key: 'removefromproduction', disabled: !job.converted, label: t("jobs.actions.removefromproduction"), onClick: () => AddToProduction(client, job.id, refetch, true) diff --git a/client/src/components/task-list/task-list.component.jsx b/client/src/components/task-list/task-list.component.jsx index 2f1d125b8..9ffa3da37 100644 --- a/client/src/components/task-list/task-list.component.jsx +++ b/client/src/components/task-list/task-list.component.jsx @@ -31,7 +31,7 @@ const DueDateRecord = ({dueDate}) => { const relativeDueDate = dueDateDayjs.fromNow(); const today = dayjs(); - if (dueDateDayjs.isAfter(today)) { + if (dueDateDayjs.isBefore(today)) { return
{dueDate}
; } else { @@ -81,13 +81,14 @@ const mapStateToProps = (state) => ({ export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent); function TaskListComponent({ - loading, - tasks, - total, - refetch, - toggleCompletedStatus, + loading, + tasks, + total, + refetch, + toggleCompletedStatus, setTaskUpsertContext, - }) { + toggleDeletedStatus + }) { const {t} = useTranslation(); const search = queryString.parse(useLocation().search); @@ -135,7 +136,7 @@ function TaskListComponent({ key: "due_date", sorter: true, sortOrder: sortcolumn === "due_date" && sortorder, - width: '5%', + width: '8%', render: (text, record) => , }, { @@ -154,7 +155,7 @@ function TaskListComponent({ width: '5%', render: (text, record) => ( - - - @@ -228,10 +229,10 @@ function TaskListComponent({ checked={deleted === "true"} onChange={(value) => handleSwitchChange('deleted', value)} /> - - diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx index 05ac709f7..f5f3a258b 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx @@ -1,15 +1,17 @@ import {Col, Form, Input, Row, Select, Switch} from "antd"; -import React, {useCallback} from "react"; +import React from "react"; import {useTranslation} from "react-i18next"; import {FormDatePicker} from "../form-date-picker/form-date-picker.component.jsx"; import {createStructuredSelector} from "reselect"; -import {selectBodyshop} from "../../redux/user/user.selectors.js"; +import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors.js"; +import dayjs from '../../utils/day'; import {connect} from "react-redux"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, + currentUser: selectCurrentUser, }); const mapDispatchToProps = (dispatch) => ({}); @@ -23,6 +25,7 @@ export default connect( export function TaskUpsertModalComponent({ form, bodyshop, + currentUser, selectedJobId, setSelectedJobId, selectedJobDetails, @@ -32,14 +35,19 @@ export function TaskUpsertModalComponent({ }) { const {t} = useTranslation(); - console.dir(data) + const datePickerPresets = [ + {label: t('tasks.date_presets.next_week'), value: dayjs().add(1, 'week')}, + {label: t('tasks.date_presets.two_weeks'), value: dayjs().add(2, 'weeks')}, + {label: t('tasks.date_presets.three_weeks'), value: dayjs().add(3, 'weeks')}, + {label: t('tasks.date_presets.one_month'), value: dayjs().add(1, 'month')}, + ]; if (loading || error) return ; return ( <> -
+ - + @@ -60,14 +68,14 @@ export function TaskUpsertModalComponent({ > {bodyshop.employees.map((employee) => ( @@ -97,7 +106,7 @@ export function TaskUpsertModalComponent({ label={t("tasks.fields.due_date")} name="due_date" > - + @@ -105,7 +114,7 @@ export function TaskUpsertModalComponent({ label={t("tasks.fields.remind_at")} name="remind_at" > - + @@ -117,7 +126,7 @@ export function TaskUpsertModalComponent({ > @@ -126,16 +135,14 @@ export function TaskUpsertModalComponent({ - {data.jobs.map((job) => ( {job.ro_number} @@ -151,7 +158,7 @@ export function TaskUpsertModalComponent({ label={t("tasks.fields.joblineid")} name="joblineid" > - {selectedJobDetails?.joblines?.map((jobline) => ( {jobline.line_desc} @@ -165,8 +172,12 @@ export function TaskUpsertModalComponent({ label={t("tasks.fields.partsorderid")} name="partsorderid" > - + {selectedJobDetails?.parts_orders?.map((partsOrder) => ( + + {partsOrder.order_number} - {partsOrder.vendor.name} + + ))} @@ -175,8 +186,12 @@ export function TaskUpsertModalComponent({ label={t("tasks.fields.billid")} name="billid" > - + {selectedJobDetails?.bills?.map((bill) => ( + + {bill.invoice_number} - {bill.vendor.name} + + ))} diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx index 7a5e0ddf6..520fe2453 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx @@ -131,6 +131,9 @@ export function TaskUpsertModalContainer({ {...values, jobid: selectedJobId, created_by: currentUser.email, bodyshopid: bodyshop.id}, ], }, + updateQueries: { + query: QUERY_GET_TASKS_JOB_DETAILS, + }, update(cache) { cache.modify({ fields: { diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 14a98d82a..be0e15d1d 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2095,12 +2095,67 @@ } } }, + "tasks": { + "date_presets": { + "next_week": "Next Week", + "two_weeks": "Two Weeks", + "three_weeks": "Three Weeks", + "one_month": "One Month" + }, + "actions": { + "new": "New Task", + "edit": "Edit Task" + }, + "titles": { + "completed": "Completed Tasks", + "deleted": "Deleted Tasks" + }, + "buttons": { + "create": "Create Task", + "complete": "Complete", + "delete": "Delete", + "edit": "Edit", + "refresh": "Refresh" + }, + "placeholders": { + "description": "Enter a description", + "jobid": "Select a Job", + "joblineid": "Select a Job Line", + "partsorderid": "Select a Parts Order", + "billid": "Select a Bill" + }, + "fields": { + "priorities": { + "low": "Low", + "medium": "Medium", + "high": "High" + }, + "title": "Title", + "joblineid": "Job Line", + "jobid": "Job", + "partsorderid": "Parts Order", + "billid": "Bill", + "description": "Description", + "due_date": "Due Date", + "actions": "Actions", + "priority": "Priority", + "remind_at": "Remind At", + "assigned_to": "Assigned To", + "completed": "Completed", + "job": { + "ro_number": "RO #" + } + } + }, "menus": { "currentuser": { "languageselector": "Language", "profile": "Profile" }, "header": { + "create_task": "Create Task", + "tasks": "Tasks", + "my_tasks": "My Tasks", "accounting": "Accounting", "accounting-payables": "Payables", "accounting-payments": "Payments", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index ba11bb4a3..519bec455 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2094,12 +2094,67 @@ } } }, + "tasks": { + "date_presets": { + "next_week": "", + "two_weeks": "", + "three_weeks": "", + "one_month": "" + }, + "actions": { + "new": "", + "edit": "" + }, + "titles": { + "completed": "", + "deleted": "" + }, + "buttons": { + "create": "", + "complete": "", + "delete": "", + "edit": "", + "refresh":"" + }, + "placeholders": { + "description": "", + "jobid": "", + "joblineid": "", + "partsorderid": "", + "billid": "" + }, + "fields": { + "priorities": { + "low": "", + "medium":"", + "high": "" + }, + "title": "", + "joblineid":"", + "jobid": "", + "partsorderid": "", + "billid": "", + "description": "", + "due_date": "", + "actions": "", + "priority": "", + "remind_at": "", + "assigned_to": "", + "completed": "", + "job": { + "ro_number": "" + } + } + }, "menus": { "currentuser": { "languageselector": "idioma", "profile": "Perfil" }, "header": { + "create_task": "", + "tasks": "", + "my_tasks": "", "accounting": "", "accounting-payables": "", "accounting-payments": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 1117003e0..c64f10047 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2094,12 +2094,67 @@ } } }, + "tasks": { + "date_presets": { + "next_week": "", + "two_weeks": "", + "three_weeks": "", + "one_month": "" + }, + "actions": { + "new": "", + "edit": "" + }, + "titles": { + "completed": "", + "deleted": "" + }, + "buttons": { + "create": "", + "complete": "", + "delete": "", + "edit": "", + "refresh":"" + }, + "placeholders": { + "description": "", + "jobid": "", + "joblineid": "", + "partsorderid": "", + "billid": "" + }, + "fields": { + "priorities": { + "low": "", + "medium":"", + "high": "" + }, + "title": "", + "joblineid":"", + "jobid": "", + "partsorderid": "", + "billid": "", + "description": "", + "due_date": "", + "actions": "", + "priority": "", + "remind_at": "", + "assigned_to": "", + "completed": "", + "job": { + "ro_number": "" + } + } + }, "menus": { "currentuser": { "languageselector": "La langue", "profile": "Profil" }, "header": { + "create_task": "", + "tasks": "", + "my_tasks": "", "accounting": "", "accounting-payables": "", "accounting-payments": "", From ae9e9f4b72955c3ae9b2d3dbaa6d16e2d2b1d745 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 27 Mar 2024 17:00:07 -0400 Subject: [PATCH 08/75] - Progress commit Signed-off-by: Dave Richer --- .../task-list/task-list.component.jsx | 3 +- .../task-list/task-list.container.jsx | 71 +++++--- .../task-upsert-modal.component.jsx | 166 +++++++++++------- .../task-upsert-modal.container.jsx | 51 ++---- client/src/graphql/jobs.queries.js | 12 -- client/src/translations/en_us/common.json | 14 ++ client/src/translations/es/common.json | 14 ++ client/src/translations/fr/common.json | 18 +- client/src/utils/undefinedtonull.js | 28 +++ 9 files changed, 237 insertions(+), 140 deletions(-) diff --git a/client/src/components/task-list/task-list.component.jsx b/client/src/components/task-list/task-list.component.jsx index 9ffa3da37..7ea01aca0 100644 --- a/client/src/components/task-list/task-list.component.jsx +++ b/client/src/components/task-list/task-list.component.jsx @@ -145,7 +145,7 @@ function TaskListComponent({ key: "priority", sorter: true, sortOrder: sortcolumn === "priority" && sortorder, - width: '5%', + width: '8%', render: (text, record) => }, @@ -157,7 +157,6 @@ function TaskListComponent({ + + + + + + + + + + + + + + + + + + + + + + - {data.jobs.map((job) => ( - - {job.ro_number} - - ))} - - - - - - - - - - - - - - - - - - - - - ); } diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx index 520fe2453..4ff02383a 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx @@ -5,14 +5,12 @@ import {useTranslation} from "react-i18next"; import {connect} from "react-redux"; import {createStructuredSelector} from "reselect"; import {MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK} from "../../graphql/tasks.queries"; -import { - QUERY_GET_TASKS_JOB_DETAILS, - QUERY_GET_TASKS_JOB_DETAILS_BY_ID -} from "../../graphql/jobs.queries.js"; +import {QUERY_GET_TASKS_JOB_DETAILS_BY_ID} from "../../graphql/jobs.queries.js"; import {toggleModalVisible} from "../../redux/modals/modals.actions"; import {selectTaskUpsert} from "../../redux/modals/modals.selectors"; import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; import TaskUpsertModalComponent from "./task-upsert-modal.component"; +import {replaceUndefinedWithNull} from "../../utils/undefinedtonull.js"; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, @@ -41,16 +39,14 @@ export function TaskUpsertModalContainer({ const [selectedJobDetails, setSelectedJobDetails] = useState(null); const [jobIdState, setJobIdState] = useState(null); - const { - loading: jobDetailsLoading, - error: jobDetailsError, - data: jobDetailsData + loading: loading, + error: error, + data: data } = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, { variables: {id: jobIdState}, - skip: !jobIdState, // Skip the query if jobIdState is null + skip: !jobIdState, }); - const {loading, error, data} = useQuery(QUERY_GET_TASKS_JOB_DETAILS); /** * Set the selected job id when the modal is opened and jobId is passed as a prop or when an existing task is passed as a prop @@ -72,16 +68,6 @@ export function TaskUpsertModalContainer({ } }, [existingTask, form, open]); - /** - * Reset the form values when the selected job id changes - */ - useEffect(() => { - form.setFieldsValue({ - joblineid: undefined, - billid: undefined, - partsorderid: undefined, - }); - }, [selectedJobId, form]); /** * Set the job id state when the selected job id changes @@ -97,10 +83,11 @@ export function TaskUpsertModalContainer({ * Set the selected job details when the job details query is successful */ useEffect(() => { - if (!jobDetailsLoading && !jobDetailsError && jobDetailsData) { - setSelectedJobDetails(jobDetailsData.jobs_by_pk); + if (!loading && !error && data) { + setSelectedJobDetails(data.jobs_by_pk); } - }, [jobDetailsLoading, jobDetailsError, jobDetailsData]); + }, [loading, error, data]); + /** * Handle the form submit @@ -109,13 +96,11 @@ export function TaskUpsertModalContainer({ */ const handleFinish = async (formValues) => { const {...values} = formValues; - if (existingTask) { await updateTask({ variables: { taskId: existingTask.id, - task: values, - jobid: selectedJobId, + task: replaceUndefinedWithNull(values) }, }); @@ -128,19 +113,19 @@ export function TaskUpsertModalContainer({ await insertTask({ variables: { taskInput: [ - {...values, jobid: selectedJobId, created_by: currentUser.email, bodyshopid: bodyshop.id}, + { + ...replaceUndefinedWithNull(values), + created_by: currentUser.email, + bodyshopid: bodyshop.id + }, ], }, - updateQueries: { - query: QUERY_GET_TASKS_JOB_DETAILS, - }, update(cache) { cache.modify({ fields: { tasks(existingTasks) { return [{ ...values, - jobid: selectedJobId, created_by: currentUser.email, bodyshopid: bodyshop.id }, ...existingTasks] @@ -154,7 +139,7 @@ export function TaskUpsertModalContainer({ form.resetFields(); toggleModalVisible(); notification["success"]({ - message: t("tasks.successes.create"), + message: t("tasks.successes.created"), }); } }; @@ -176,7 +161,7 @@ export function TaskUpsertModalContainer({ > diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index a2a2e43eb..184b370ca 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -2156,18 +2156,6 @@ export const QUERY_GET_TASKS_JOB_DETAILS_BY_ID = gql` } `; -export const QUERY_GET_TASKS_JOB_DETAILS = gql` - query GetTasksJobDetails { - jobs { - id - ro_number - ownr_fn - ownr_ln - ownr_co_nm - } - } -`; - export const GET_JOB_FOR_CC_CONTRACT = gql` query GET_JOB_FOR_CC_CONTRACT($id: uuid!) { jobs_by_pk(id: $id) { diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index be0e15d1d..e8c6ea50b 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -900,6 +900,7 @@ }, "titles": { "joblifecycle": "Job Life Cycles", + "tasks": "Tasks", "labhours": "Total Body Hours", "larhours": "Total Refinish Hours", "monthlyemployeeefficiency": "Monthly Employee Efficiency", @@ -2096,6 +2097,18 @@ } }, "tasks": { + "failures": { + "created": "Failed to create Task.", + "deleted": "Failed to toggle Task deletion.", + "updated": "Failed to update Task.", + "completed": "Failed to toggle Task completion." + }, + "successes": { + "created": "Task created successfully.", + "deleted": "Toggled Task deletion successfully.", + "updated": "Task updated successfully.", + "completed": "Toggled Task completion successfully." + }, "date_presets": { "next_week": "Next Week", "two_weeks": "Two Weeks", @@ -3117,6 +3130,7 @@ "accounting-receivables": "Receivables | {{app}}", "app": "", "bc": { + "tasks": "Tasks", "accounting-payables": "Payables", "accounting-payments": "Payments", "accounting-receivables": "Receivables", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 519bec455..bed1bb7b5 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -900,6 +900,7 @@ }, "titles": { "joblifecycle": "", + "tasks": "", "labhours": "", "larhours": "", "monthlyemployeeefficiency": "", @@ -2095,6 +2096,18 @@ } }, "tasks": { + "failures": { + "created": "", + "deleted": "", + "updated": "", + "completed": "" + }, + "successes": { + "created": "", + "deleted": "", + "updated": "", + "completed": "" + }, "date_presets": { "next_week": "", "two_weeks": "", @@ -3116,6 +3129,7 @@ "accounting-receivables": "", "app": "", "bc": { + "tasks": "", "accounting-payables": "", "accounting-payments": "", "accounting-receivables": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index c64f10047..947c440c4 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -900,7 +900,8 @@ }, "titles": { "joblifecycle": "", - "labhours": "", + "tasks": "", + "labhours": "", "larhours": "", "monthlyemployeeefficiency": "", "monthlyjobcosting": "", @@ -2095,6 +2096,18 @@ } }, "tasks": { + "failures": { + "created": "", + "deleted": "", + "updated": "", + "completed": "" + }, + "successes": { + "created": "", + "deleted": "", + "updated": "", + "completed": "" + }, "date_presets": { "next_week": "", "two_weeks": "", @@ -3116,7 +3129,8 @@ "accounting-receivables": "", "app": "", "bc": { - "accounting-payables": "", + "tasks": "", + "accounting-payables": "", "accounting-payments": "", "accounting-receivables": "", "availablejobs": "", diff --git a/client/src/utils/undefinedtonull.js b/client/src/utils/undefinedtonull.js index 56a67947a..03bd4f9fe 100644 --- a/client/src/utils/undefinedtonull.js +++ b/client/src/utils/undefinedtonull.js @@ -1,3 +1,13 @@ +/** + * Replaces undefined values with null in an object. + * Optionally, you can specify keys to replace. + * If keys are specified, only those keys will be replaced. + * If no keys are specified, all undefined values will be replaced. + * @param obj + * @param keys + * @returns {*} + * @constructor + */ export default function UndefinedToNull(obj, keys) { Object.keys(obj).forEach((key) => { if (keys && keys.indexOf(key) >= 0) { @@ -8,3 +18,21 @@ export default function UndefinedToNull(obj, keys) { }); return obj; } + +/** + * Replaces undefined values with null in an object. Optionally, you can specify keys to replace. If keys are specified, only those keys will be replaced. If no keys are specified, all undefined values will be replaced. + * @param obj + * @param keys + * @returns {{[p: string]: unknown}} + */ +export function replaceUndefinedWithNull(obj, keys) { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => { + if (keys) { + return [key, (keys.includes(key) && value === undefined) ? null : value]; + } else { + return [key, value === undefined ? null : value]; + } + }) + ); +} From dc22b96bed9added07d8948447c77975bb7821d8 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 28 Mar 2024 12:30:14 -0400 Subject: [PATCH 09/75] - Progress commit Signed-off-by: Dave Richer --- .../task-list/task-list.component.jsx | 11 +- .../task-list/task-list.container.jsx | 10 +- .../task-upsert-modal.component.jsx | 8 +- .../task-upsert-modal.container.jsx | 18 +- client/src/graphql/tasks.queries.js | 253 ++++++++++++++---- .../jobs-detail.page.component.jsx | 10 +- .../src/pages/tasks/tasks.page.component.jsx | 4 +- client/src/translations/en_us/common.json | 2 + 8 files changed, 250 insertions(+), 66 deletions(-) diff --git a/client/src/components/task-list/task-list.component.jsx b/client/src/components/task-list/task-list.component.jsx index 7ea01aca0..8a63a8ede 100644 --- a/client/src/components/task-list/task-list.component.jsx +++ b/client/src/components/task-list/task-list.component.jsx @@ -84,10 +84,13 @@ function TaskListComponent({ loading, tasks, total, + titleTranslation, refetch, toggleCompletedStatus, setTaskUpsertContext, - toggleDeletedStatus + toggleDeletedStatus, + relationshipType, + relationshipId }) { const {t} = useTranslation(); @@ -186,7 +189,9 @@ function TaskListComponent({ const handleCreateTask = () => { setTaskUpsertContext({ actions: {}, - context: {}, + context: { + [relationshipType]: relationshipId, + }, }); }; @@ -241,7 +246,7 @@ function TaskListComponent({ return (
); } diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx index 3f60b1d41..b84d15579 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx @@ -42,7 +42,7 @@ export function TaskUpsertModalComponent({ {label: t('tasks.date_presets.three_weeks'), value: dayjs().add(3, 'weeks')}, {label: t('tasks.date_presets.one_month'), value: dayjs().add(1, 'month')}, ]; - + const clearRelations = () => { form.setFieldsValue({ billid: null, @@ -61,8 +61,8 @@ export function TaskUpsertModalComponent({ clearRelations(); }; - - if (!data || loading || error) return ; + + if (loading || error) return ; return ( <> @@ -115,7 +115,7 @@ export function TaskUpsertModalComponent({ { - if (jobId || existingTask?.id) { - setSelectedJobId(jobId || existingTask.jobid); + if (jobid || existingTask?.id) { + setSelectedJobId(jobid || existingTask.jobid); } - }, [jobId, existingTask]); + }, [jobid, existingTask]); /** * Set the form values when the modal is opened and an existing task is passed as a prop @@ -65,9 +65,11 @@ export function TaskUpsertModalContainer({ form.setFieldsValue(existingTask); } else if (!existingTask && open) { form.resetFields(); + if (joblineid) form.setFieldsValue({joblineid}); + if (billid) form.setFieldsValue({billid}); + if (partsorderid) form.setFieldsValue({partsorderid}); } - }, [existingTask, form, open]); - + }, [existingTask, form, open, joblineid, billid, partsorderid]); /** * Set the job id state when the selected job id changes @@ -125,10 +127,10 @@ export function TaskUpsertModalContainer({ fields: { tasks(existingTasks) { return [{ - ...values, + ...values, created_by: currentUser.email, bodyshopid: bodyshop.id - }, ...existingTasks] + }, ...existingTasks] }, }, }); diff --git a/client/src/graphql/tasks.queries.js b/client/src/graphql/tasks.queries.js index d19b70315..525cc4ae0 100644 --- a/client/src/graphql/tasks.queries.js +++ b/client/src/graphql/tasks.queries.js @@ -42,12 +42,214 @@ export const QUERY_ALL_TASKS_PAGINATED = gql` } `; -/** - * My tasks paginated query - * @type {DocumentNode} - */ +const PARTIAL_TASK_FIELDS = gql` + fragment TaskFields on tasks { + id + created_at + updated_at + title + description + deleted + deleted_at + due_date + created_by + assigned_to + completed + completed_at + remind_at + priority + job { + id + ro_number + joblines { + id + line_desc + } + bills { + id + vendor { + name + } + invoice_number + } + parts_orders { + id + vendor { + name + } + order_number + } + } + jobid + joblineid + partsorderid + billid + } +`; + +// Query for joblineid +export const QUERY_JOBLINE_TASKS_PAGINATED = gql` + ${PARTIAL_TASK_FIELDS} + query QUERY_JOBLINE_TASKS_PAGINATED( + $offset: Int + $limit: Int + $joblineid: uuid! + $bodyshop: uuid! + $deleted: Boolean + $completed: Boolean + $order: [tasks_order_by!]! + ) { + tasks( + offset: $offset + limit: $limit + order_by: $order + where: { + joblineid: {_eq: $joblineid}, + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + completed: {_eq: $completed} + } + ) { + ...TaskFields + } + tasks_aggregate( + where: { + joblineid: {_eq: $joblineid}, + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + completed: {_eq: $completed} + } + ) { + aggregate { + count + } + } + } +`; + +// Query for partsorderid +export const QUERY_PARTSORDER_TASKS_PAGINATED = gql` + ${PARTIAL_TASK_FIELDS} + query QUERY_PARTSORDER_TASKS_PAGINATED( + $offset: Int + $limit: Int + $partsorderid: uuid! + $bodyshop: uuid! + $deleted: Boolean + $completed: Boolean + $order: [tasks_order_by!]! + ) { + tasks( + offset: $offset + limit: $limit + order_by: $order + where: { + partsorderid: {_eq: $partsorderid}, + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + completed: {_eq: $completed} + } + ) { + ...TaskFields + } + tasks_aggregate( + where: { + partsorderid: {_eq: $partsorderid}, + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + completed: {_eq: $completed} + } + ) { + aggregate { + count + } + } + } +`; + +// Query for billid +export const QUERY_BILL_TASKS_PAGINATED = gql` + ${PARTIAL_TASK_FIELDS} + query QUERY_BILL_TASKS_PAGINATED( + $offset: Int + $limit: Int + $billid: uuid! + $bodyshop: uuid! + $deleted: Boolean + $completed: Boolean + $order: [tasks_order_by!]! + ) { + tasks( + offset: $offset + limit: $limit + order_by: $order + where: { + billid: {_eq: $billid}, + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + completed: {_eq: $completed} + } + ) { + ...TaskFields + } + tasks_aggregate( + where: { + billid: {_eq: $billid}, + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + completed: {_eq: $completed} + } + ) { + aggregate { + count + } + } + } +`; + +// Use the fragment in your queries +export const QUERY_JOB_TASKS_PAGINATED = gql` + ${PARTIAL_TASK_FIELDS} + query QUERY_JOB_TASKS_PAGINATED( + $offset: Int + $limit: Int + $jobid: uuid! + $bodyshop: uuid! + $deleted: Boolean + $completed: Boolean + $order: [tasks_order_by!]! + ) { + tasks( + offset: $offset + limit: $limit + order_by: $order + where: { + jobid: {_eq: $jobid}, + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + completed: {_eq: $completed} + } + ) { + ...TaskFields + } + tasks_aggregate( + where: { + jobid: {_eq: $jobid}, + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + completed: {_eq: $completed} + } + ) { + aggregate { + count + } + } + } +`; + export const QUERY_MY_TASKS_PAGINATED = gql` - query QUERY_TASKS_PAGINATED( + ${PARTIAL_TASK_FIELDS} + query QUERY_MY_TASKS_PAGINATED( $offset: Int $limit: Int $user: String! @@ -67,46 +269,7 @@ export const QUERY_MY_TASKS_PAGINATED = gql` completed: {_eq: $completed} } ) { - id - created_at - updated_at - title - description - deleted - deleted_at - due_date - created_by - assigned_to - completed - completed_at - remind_at - priority - job { - id - ro_number - joblines { - id - line_desc - } - bills { - id - vendor { - name - } - invoice_number - } - parts_orders { - id - vendor { - name - } - order_number - } - } - jobid - joblineid - partsorderid - billid + ...TaskFields } tasks_aggregate( where: { diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index 1027001f4..a6b684e88 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -16,7 +16,7 @@ import dayjs from "../../utils/day"; import queryString from "query-string"; import React, {useEffect, useState} from "react"; import {useTranslation} from "react-i18next"; -import {FaHardHat, FaRegStickyNote, FaShieldAlt} from "react-icons/fa"; +import {FaHardHat, FaRegStickyNote, FaShieldAlt, FaTasks} from "react-icons/fa"; import {connect} from "react-redux"; import {useLocation, useNavigate} from "react-router-dom"; import {createStructuredSelector} from "reselect"; @@ -54,6 +54,8 @@ import JobProfileDataWarning from "../../components/job-profile-data-warning/job import {DateTimeFormat} from "../../utils/DateFormatter"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component"; +import TaskListContainer from "../../components/task-list/task-list.container.jsx"; +import {QUERY_JOB_TASKS_PAGINATED} from "../../graphql/tasks.queries.js"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -376,6 +378,12 @@ export function JobsDetailPage({ label: t("jobs.labels.audit"), children: , }, + { + key: 'tasks', + icon: , + label: t("menus.jobsdetail.tasks"), + children: + }, ]} /> diff --git a/client/src/pages/tasks/tasks.page.component.jsx b/client/src/pages/tasks/tasks.page.component.jsx index 253bc9c4c..34cff9fc3 100644 --- a/client/src/pages/tasks/tasks.page.component.jsx +++ b/client/src/pages/tasks/tasks.page.component.jsx @@ -1,11 +1,13 @@ import React from "react"; import TaskListContainer from "../../components/task-list/task-list.container.jsx"; +import {QUERY_MY_TASKS_PAGINATED} from "../../graphql/tasks.queries.js"; export default function TasksPageComponent({bodyshop, currentUser}) { return (
- +
); } diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index e8c6ea50b..aedcbdc52 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2120,6 +2120,8 @@ "edit": "Edit Task" }, "titles": { + "job_tasks": "Job Tasks", + "my_tasks": "My Tasks", "completed": "Completed Tasks", "deleted": "Deleted Tasks" }, From 9f9fa3b9523587f60b5f2ee51d88f52e4c9ad1b2 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 28 Mar 2024 15:02:06 -0400 Subject: [PATCH 10/75] - Progress commit Signed-off-by: Dave Richer --- .../jobs-detail-header.component.jsx | 6 +-- .../task-list/task-list.container.jsx | 13 ++++++ .../task-upsert-modal.container.jsx | 44 ++++++++++++------- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx index 003143a86..038e6ef99 100644 --- a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx +++ b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx @@ -119,7 +119,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) { {job.cccontracts.map((c, index) => ( - + {`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`} {index !== job.cccontracts.length - 1 ? "," : null} @@ -222,10 +222,10 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) { } >
- + {`${job.plate_no || t("general.labels.na")} (${`${job.plate_st || t("general.labels.na")}`})`} - + {`${job.v_vin || t("general.labels.na")}`} {bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? ( job.v_vin?.length !== 17 ? ( diff --git a/client/src/components/task-list/task-list.container.jsx b/client/src/components/task-list/task-list.container.jsx index 2dbe385f4..191f46a5a 100644 --- a/client/src/components/task-list/task-list.container.jsx +++ b/client/src/components/task-list/task-list.container.jsx @@ -52,6 +52,19 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r }; }, [refetch]); + useEffect(() => { + const handleTaskUpdated = (event) => { + refetch(); + }; + + window.addEventListener('taskUpdated', handleTaskUpdated); + + // Clean up the event listener when the component is unmounted. + return () => { + window.removeEventListener('taskUpdated', handleTaskUpdated); + }; + }, [refetch]); + /** * Toggle task completed mutation */ diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx index d88f9f401..7bee7f1bc 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx @@ -30,7 +30,6 @@ export function TaskUpsertModalContainer({ const {t} = useTranslation(); const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK); const [updateTask] = useMutation(MUTATION_UPDATE_TASK); - const {open, context, actions} = taskUpsert; const {jobid, existingTask, joblineid, billid, partsorderid} = context; const {refetch} = actions; @@ -55,6 +54,9 @@ export function TaskUpsertModalContainer({ if (jobid || existingTask?.id) { setSelectedJobId(jobid || existingTask.jobid); } + return () => { + setSelectedJobId(null); + }; }, [jobid, existingTask]); /** @@ -105,7 +107,9 @@ export function TaskUpsertModalContainer({ task: replaceUndefinedWithNull(values) }, }); - + window.dispatchEvent( new CustomEvent('taskUpdated', { + detail: { message: 'A task has been created or edited.' }, + })); notification["success"]({ message: t("tasks.successes.updated"), }); @@ -116,30 +120,36 @@ export function TaskUpsertModalContainer({ variables: { taskInput: [ { - ...replaceUndefinedWithNull(values), + ...values, created_by: currentUser.email, bodyshopid: bodyshop.id }, ], }, - update(cache) { - cache.modify({ - fields: { - tasks(existingTasks) { - return [{ - ...values, - created_by: currentUser.email, - bodyshopid: bodyshop.id - }, ...existingTasks] - }, - }, - }); - }, - }); + // TODO: Consult Patrick, because this fails on relationship data, and an event emitter is just much easier to use + // update(cache) { + // cache.modify({ + // fields: { + // tasks(existingTasks) { + // return [{ + // ...values, + // jobid: selectedJobId || values.jobid, + // created_by: currentUser.email, + // bodyshopid: bodyshop.id + // }, ...existingTasks] + // }, + // }, + // }); + // }, + + }); if (refetch) await refetch(); form.resetFields(); toggleModalVisible(); + window.dispatchEvent( new CustomEvent('taskUpdated', { + detail: { message: 'A task has been created or edited.' }, + })); notification["success"]({ message: t("tasks.successes.created"), }); From ae07f71e76b4255aff67a1bed4a1743081570a9a Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 1 Apr 2024 14:45:55 -0400 Subject: [PATCH 11/75] - Progress Commit Signed-off-by: Dave Richer --- .../bills-list-table.component.jsx | 50 ++++++++---- .../job-lines-expander.component.jsx | 15 +++- .../job-detail-lines/job-lines.component.jsx | 17 +++- .../jobs-list-paginated.component.jsx | 1 - .../jobs-list/jobs-list.component.jsx | 6 +- .../parts-order-list-table.component.jsx | 79 ++++++++++++------- .../task-list/task-list.component.jsx | 39 +++++++-- .../task-list/task-list.container.jsx | 7 +- .../task-upsert-modal.component.jsx | 2 +- client/src/graphql/bills.queries.js | 4 + client/src/graphql/jobs.queries.js | 10 +++ client/src/graphql/tasks.queries.js | 12 +++ .../jobs-detail.page.component.jsx | 14 ++-- .../src/pages/tasks/tasks.page.component.jsx | 2 +- client/src/translations/en_us/common.json | 15 +++- client/src/translations/es/common.json | 14 +++- client/src/translations/fr/common.json | 14 +++- 17 files changed, 223 insertions(+), 78 deletions(-) diff --git a/client/src/components/bills-list-table/bills-list-table.component.jsx b/client/src/components/bills-list-table/bills-list-table.component.jsx index 5db868406..ad764f1e1 100644 --- a/client/src/components/bills-list-table/bills-list-table.component.jsx +++ b/client/src/components/bills-list-table/bills-list-table.component.jsx @@ -1,29 +1,35 @@ -import { EditFilled, SyncOutlined } from "@ant-design/icons"; -import { Button, Card, Checkbox, Input, Space, Table } from "antd"; -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { selectJobReadOnly } from "../../redux/application/application.selectors"; -import { setModalContext } from "../../redux/modals/modals.actions"; -import { selectBodyshop } from "../../redux/user/user.selectors"; +import {EditFilled, SyncOutlined} from "@ant-design/icons"; +import {Button, Card, Checkbox, Input, Space, Table} from "antd"; +import React, {useState} from "react"; +import {useTranslation} from "react-i18next"; +import {connect} from "react-redux"; +import {createStructuredSelector} from "reselect"; +import {selectJobReadOnly} from "../../redux/application/application.selectors"; +import {setModalContext} from "../../redux/modals/modals.actions"; +import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import { DateFormatter } from "../../utils/DateFormatter"; -import { alphaSort, dateSort } from "../../utils/sorters"; -import { TemplateList } from "../../utils/TemplateConstants"; +import {DateFormatter} from "../../utils/DateFormatter"; +import {alphaSort, dateSort} from "../../utils/sorters"; +import {TemplateList} from "../../utils/TemplateConstants"; import BillDeleteButton from "../bill-delete-button/bill-delete-button.component"; import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; +import {FaTasks} from "react-icons/fa"; const mapStateToProps = createStructuredSelector({ jobRO: selectJobReadOnly, - bodyshop: selectBodyshop + bodyshop: selectBodyshop, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })), setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), - setReconciliationContext: (context) => dispatch(setModalContext({ context: context, modal: "reconciliation" })) + setReconciliationContext: (context) => dispatch(setModalContext({ + context: context, + modal: "reconciliation" + })), + setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})), }); export function BillsListTableComponent({ @@ -34,7 +40,9 @@ export function BillsListTableComponent({ handleOnRowClick, setPartsOrderContext, setBillEnterContext, - setReconciliationContext + setReconciliationContext, + setTaskUpsertContext, + currentUser, }) { const { t } = useTranslation(); @@ -48,6 +56,8 @@ export function BillsListTableComponent({ const Templates = TemplateList("bill"); const bills = billsQuery.data ? billsQuery.data.bills : []; const { refetch } = billsQuery; + + const recordActions = (record, showView = false) => ( {showView && ( @@ -55,6 +65,16 @@ export function BillsListTableComponent({ )} + ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander); -export function JobLinesExpander({ jobline, jobid, bodyshop }) { +export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) { const { t } = useTranslation(); const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, { fetchPolicy: "network-only", @@ -128,6 +134,9 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) { } /> +
+ + ); } diff --git a/client/src/components/job-detail-lines/job-lines.component.jsx b/client/src/components/job-detail-lines/job-lines.component.jsx index 8dfa60132..fc209d210 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -42,6 +42,7 @@ import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job- import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container"; import JobLinesExpander from "./job-lines-expander.component"; import JobLinesPartPriceChange from "./job-lines-part-price-change.component"; +import {FaTasks} from "react-icons/fa"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -52,7 +53,8 @@ const mapStateToProps = createStructuredSelector({ const mapDispatchToProps = (dispatch) => ({ setJobLineEditContext: (context) => dispatch(setModalContext({ context: context, modal: "jobLineEdit" })), setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })), - setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })) + setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), + setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})), }); export function JobLinesComponent({ @@ -67,7 +69,8 @@ export function JobLinesComponent({ job, setJobLineEditContext, form, - setBillEnterContext + setBillEnterContext, + setTaskUpsertContext }) { const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK); const { @@ -331,6 +334,16 @@ export function JobLinesComponent({ > + - + ), - }, - ]; + } + ); const [state, setState] = useState({ sortedInfo: {}, @@ -190,6 +205,7 @@ function TaskListComponent({ setTaskUpsertContext({ actions: {}, context: { + jobid: parentJobId, [relationshipType]: relationshipId, }, }); @@ -219,6 +235,15 @@ function TaskListComponent({ const tasksExtra = useCallback(() => { return ( + {!onlyMine && ( + handleSwitchChange('mine', value)} + /> +)} } unCheckedChildren={} @@ -242,7 +267,7 @@ function TaskListComponent({ ); - }, [refetch, deleted, completed]); + }, [refetch, deleted, completed, mine]); return ( ); } diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx index b84d15579..a00741d77 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx @@ -187,7 +187,7 @@ export function TaskUpsertModalComponent({ }, ]} > - {bodyshop.employees.filter(x => x.active).map((employee) => ( {employee.first_name} {employee.last_name} diff --git a/client/src/graphql/bills.queries.js b/client/src/graphql/bills.queries.js index 355a123dd..49bbaebbc 100644 --- a/client/src/graphql/bills.queries.js +++ b/client/src/graphql/bills.queries.js @@ -59,6 +59,10 @@ export const QUERY_BILLS_BY_JOBID = gql` name email } + tasks { + id + due_date + } order_date deliver_by return diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 92e490316..3e340152b 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -100,6 +100,11 @@ export const QUERY_ALL_ACTIVE_JOBS = gql` suspended est_ct_fn est_ct_ln + tasks_aggregate(where: { completed: { _eq: false }, deleted: { _eq: false } }) { + aggregate { + count + } + } } } `; @@ -499,6 +504,11 @@ export const QUERY_JOB_COSTING_DETAILS = gql` export const GET_JOB_BY_PK = gql` query GET_JOB_BY_PK($id: uuid!) { jobs_by_pk(id: $id) { + tasks_aggregate(where: { completed: { _eq: false }, deleted: { _eq: false } }) { + aggregate { + count + } + } actual_completion actual_delivery actual_in diff --git a/client/src/graphql/tasks.queries.js b/client/src/graphql/tasks.queries.js index 525cc4ae0..615a04963 100644 --- a/client/src/graphql/tasks.queries.js +++ b/client/src/graphql/tasks.queries.js @@ -97,6 +97,7 @@ export const QUERY_JOBLINE_TASKS_PAGINATED = gql` $bodyshop: uuid! $deleted: Boolean $completed: Boolean + $assigned_to: String $order: [tasks_order_by!]! ) { tasks( @@ -107,6 +108,7 @@ export const QUERY_JOBLINE_TASKS_PAGINATED = gql` joblineid: {_eq: $joblineid}, bodyshopid: {_eq: $bodyshop}, deleted: {_eq: $deleted}, + assigned_to: {_eq: $assigned_to}, completed: {_eq: $completed} } ) { @@ -117,6 +119,7 @@ export const QUERY_JOBLINE_TASKS_PAGINATED = gql` joblineid: {_eq: $joblineid}, bodyshopid: {_eq: $bodyshop}, deleted: {_eq: $deleted}, + assigned_to: {_eq: $assigned_to}, completed: {_eq: $completed} } ) { @@ -137,6 +140,7 @@ export const QUERY_PARTSORDER_TASKS_PAGINATED = gql` $bodyshop: uuid! $deleted: Boolean $completed: Boolean + $assigned_to: String $order: [tasks_order_by!]! ) { tasks( @@ -147,6 +151,7 @@ export const QUERY_PARTSORDER_TASKS_PAGINATED = gql` partsorderid: {_eq: $partsorderid}, bodyshopid: {_eq: $bodyshop}, deleted: {_eq: $deleted}, + assigned_to: {_eq: $assigned_to}, completed: {_eq: $completed} } ) { @@ -157,6 +162,7 @@ export const QUERY_PARTSORDER_TASKS_PAGINATED = gql` partsorderid: {_eq: $partsorderid}, bodyshopid: {_eq: $bodyshop}, deleted: {_eq: $deleted}, + assigned_to: {_eq: $assigned_to}, completed: {_eq: $completed} } ) { @@ -177,6 +183,7 @@ export const QUERY_BILL_TASKS_PAGINATED = gql` $bodyshop: uuid! $deleted: Boolean $completed: Boolean + $assigned_to: String $order: [tasks_order_by!]! ) { tasks( @@ -187,6 +194,7 @@ export const QUERY_BILL_TASKS_PAGINATED = gql` billid: {_eq: $billid}, bodyshopid: {_eq: $bodyshop}, deleted: {_eq: $deleted}, + assigned_to: {_eq: $assigned_to}, completed: {_eq: $completed} } ) { @@ -197,6 +205,7 @@ export const QUERY_BILL_TASKS_PAGINATED = gql` billid: {_eq: $billid}, bodyshopid: {_eq: $bodyshop}, deleted: {_eq: $deleted}, + assigned_to: {_eq: $assigned_to}, completed: {_eq: $completed} } ) { @@ -217,6 +226,7 @@ export const QUERY_JOB_TASKS_PAGINATED = gql` $bodyshop: uuid! $deleted: Boolean $completed: Boolean + $assigned_to: String $order: [tasks_order_by!]! ) { tasks( @@ -227,6 +237,7 @@ export const QUERY_JOB_TASKS_PAGINATED = gql` jobid: {_eq: $jobid}, bodyshopid: {_eq: $bodyshop}, deleted: {_eq: $deleted}, + assigned_to: {_eq: $assigned_to}, completed: {_eq: $completed} } ) { @@ -237,6 +248,7 @@ export const QUERY_JOB_TASKS_PAGINATED = gql` jobid: {_eq: $jobid}, bodyshopid: {_eq: $bodyshop}, deleted: {_eq: $deleted}, + assigned_to: {_eq: $assigned_to}, completed: {_eq: $completed} } ) { diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index ca42fb86f..423287a8a 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -8,7 +8,7 @@ import Icon, { SyncOutlined, ToolFilled } from "@ant-design/icons"; -import {Button, Divider, Form, notification, Space, Tabs} from "antd"; +import {Badge, Button, Divider, Form, notification, Space, Tabs} from "antd"; import {PageHeader} from "@ant-design/pro-layout"; import Axios from "axios"; @@ -55,7 +55,7 @@ import ScheduleJobModalContainer import {insertAuditTrail} from "../../redux/application/application.actions"; import {selectJobReadOnly} from "../../redux/application/application.selectors"; import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; +import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import UndefinedToNull from "../../utils/undefinedtonull"; import _ from "lodash"; @@ -69,7 +69,8 @@ import {QUERY_JOB_TASKS_PAGINATED} from "../../graphql/tasks.queries.js"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, - jobRO: selectJobReadOnly + jobRO: selectJobReadOnly, + currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ setPrintCenterContext: (context) => dispatch(setModalContext({ @@ -90,6 +91,7 @@ export function JobsDetailPage({ job, mutationUpdateJob, handleSubmit, + currentUser, insertAuditTrail, refetch }) { @@ -391,8 +393,10 @@ export function JobsDetailPage({ { key: 'tasks', icon: , - label: t("menus.jobsdetail.tasks"), - children: + label: + {t("jobs.labels.tasks")}{job.tasks_aggregate.aggregate.count > 0 && } + , + children: }, ]} /> diff --git a/client/src/pages/tasks/tasks.page.component.jsx b/client/src/pages/tasks/tasks.page.component.jsx index 34cff9fc3..b3758f48a 100644 --- a/client/src/pages/tasks/tasks.page.component.jsx +++ b/client/src/pages/tasks/tasks.page.component.jsx @@ -6,7 +6,7 @@ export default function TasksPageComponent({bodyshop, currentUser}) { return (
-
); diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 82eca9aeb..d1c14f89b 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1524,7 +1524,8 @@ "voiding": "Error voiding Job. {{error}}" }, "fields": { - "actual_completion": "Actual Completion", + "active_tasks": "Active Tasks", + "actual_completion": "Actual Completion", "actual_delivery": "Actual Delivery", "actual_in": "Actual In", "adjustment_bottom_line": "Adjustments", @@ -1840,7 +1841,8 @@ "appointmentconfirmation": "Send confirmation to customer?", "associationwarning": "Any changes to associations will require updating the data from the new parent record to the Job.", "audit": "Audit Trail", - "available": "Available", + "tasks": "Tasks", + "available": "Available", "availablejobs": "Available Jobs", "ca_bc_pvrt": { "days": "Days", @@ -2121,6 +2123,7 @@ }, "titles": { "job_tasks": "Job Tasks", + "mine": "My Tasks", "my_tasks": "My Tasks", "completed": "Completed Tasks", "deleted": "Deleted Tasks" @@ -2130,14 +2133,17 @@ "complete": "Complete", "delete": "Delete", "edit": "Edit", - "refresh": "Refresh" + "refresh": "Refresh", + "myTasks": "Mine", + "allTasks": "All" }, "placeholders": { "description": "Enter a description", "jobid": "Select a Job", "joblineid": "Select a Job Line", "partsorderid": "Select a Parts Order", - "billid": "Select a Bill" + "billid": "Select a Bill", + "assigned_to": "Select an Employee" }, "fields": { "priorities": { @@ -3127,6 +3133,7 @@ } }, "titles": { + "tasks": "Tasks", "accounting-payables": "Payables | {{app}}", "accounting-payments": "Payments | {{app}}", "accounting-receivables": "Receivables | {{app}}", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 75b7c323a..8d412e06b 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1524,6 +1524,7 @@ "voiding": "" }, "fields": { + "active_tasks": "", "actual_completion": "Realización real", "actual_delivery": "Entrega real", "actual_in": "Real en", @@ -1840,7 +1841,8 @@ "appointmentconfirmation": "¿Enviar confirmación al cliente?", "associationwarning": "", "audit": "", - "available": "", + "tasks": "", + "available": "", "availablejobs": "", "ca_bc_pvrt": { "days": "", @@ -2120,6 +2122,8 @@ "edit": "" }, "titles": { + "job_tasks": "", + "ny_tasks": "", "completed": "", "deleted": "" }, @@ -2128,14 +2132,17 @@ "complete": "", "delete": "", "edit": "", - "refresh":"" + "refresh":"", + "myTasks": "", + "allTasks": "" }, "placeholders": { "description": "", "jobid": "", "joblineid": "", "partsorderid": "", - "billid": "" + "billid": "", + "assigned_to": "" }, "fields": { "priorities": { @@ -3125,6 +3132,7 @@ } }, "titles": { + "tasks": "", "accounting-payables": "", "accounting-payments": "", "accounting-receivables": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index fa57f97f4..e39928eb3 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1524,6 +1524,7 @@ "voiding": "" }, "fields": { + "active_tasks": "", "actual_completion": "Achèvement réel", "actual_delivery": "Livraison réelle", "actual_in": "En réel", @@ -1840,7 +1841,8 @@ "appointmentconfirmation": "Envoyer une confirmation au client?", "associationwarning": "", "audit": "", - "available": "", + "tasks": "", + "available": "", "availablejobs": "", "ca_bc_pvrt": { "days": "", @@ -2120,6 +2122,8 @@ "edit": "" }, "titles": { + "job_tasks": "", + "ny_tasks": "", "completed": "", "deleted": "" }, @@ -2128,14 +2132,17 @@ "complete": "", "delete": "", "edit": "", - "refresh":"" + "refresh":"", + "myTasks": "", + "allTasks": "" }, "placeholders": { "description": "", "jobid": "", "joblineid": "", "partsorderid": "", - "billid": "" + "billid": "", + "assigned_to": "" }, "fields": { "priorities": { @@ -3129,6 +3136,7 @@ "accounting-payments": "", "accounting-receivables": "", "app": "", + "tasks": "", "bc": { "tasks": "", "accounting-payables": "", From 1343b68cc601b6566223076ade9e484869c1616e Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 1 Apr 2024 16:34:31 -0400 Subject: [PATCH 12/75] - Progress Commit Signed-off-by: Dave Richer --- .../task-list/task-list.component.jsx | 2 ++ .../task-list/task-list.container.jsx | 31 +++++++++---------- .../jobs-detail.page.component.jsx | 12 +++++++ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/client/src/components/task-list/task-list.component.jsx b/client/src/components/task-list/task-list.component.jsx index 0daf79ebb..a331612b7 100644 --- a/client/src/components/task-list/task-list.component.jsx +++ b/client/src/components/task-list/task-list.component.jsx @@ -118,6 +118,8 @@ function TaskListComponent({ dataIndex: "assigned_to", key: "assigned_to", width: '10%', + sorter: true, + sortOrder: sortcolumn === "assigned_to" && sortorder, } ); } diff --git a/client/src/components/task-list/task-list.container.jsx b/client/src/components/task-list/task-list.container.jsx index 5db08ab73..d2b24ffcb 100644 --- a/client/src/components/task-list/task-list.container.jsx +++ b/client/src/components/task-list/task-list.container.jsx @@ -46,18 +46,9 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r * Refetch tasks when a task is updated */ useEffect(() => { - const handleTaskUpdated = () => { - refetch().catch((e) => { - console.error(`Something went wrong fetching tasks: ${e.message || ''}`); - }); + const handleTaskUpdated = async (event) => { + await refetch().catch(e => `Something went wrong fetching tasks: ${e.message || ''}`); }; - }, [refetch]); - - useEffect(() => { - const handleTaskUpdated = (event) => { - refetch(); - }; - window.addEventListener('taskUpdated', handleTaskUpdated); // Clean up the event listener when the component is unmounted. @@ -87,9 +78,12 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r completed_at: completed_at } }); - refetch().catch((e) => { - console.error(`Something went wrong fetching tasks: ${e.message || ''}`); - }); + // refetch().catch((e) => { + // console.error(`Something went wrong fetching tasks: ${e.message || ''}`); + // }); + window.dispatchEvent( new CustomEvent('taskUpdated', { + detail: { message: 'A task has been completed.' }, + })); notification["success"]({ message: t("tasks.successes.completed"), }); @@ -121,9 +115,12 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r deleted_at: deleted_at } }); - refetch().catch((e) => { - console.error(`Something went wrong fetching tasks: ${e.message || ''}`); - }); + window.dispatchEvent( new CustomEvent('taskUpdated', { + detail: { message: 'A task has been deleted.' }, + })); + // refetch().catch((e) => { + // console.error(`Something went wrong fetching tasks: ${e.message || ''}`); + // }); notification["success"]({ message: t("tasks.successes.deleted"), }); diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index 423287a8a..0dd37b81c 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -109,6 +109,18 @@ export function JobsDetailPage({ form.resetFields(); }, [form, job]); + useEffect(() => { + const handleTaskUpdated = async (event) => { + await refetch().catch(e => `Something went wrong fetching tasks: ${e.message || ''}`); + }; + window.addEventListener('taskUpdated', handleTaskUpdated); + + // Clean up the event listener when the component is unmounted. + return () => { + window.removeEventListener('taskUpdated', handleTaskUpdated); + }; + }, [refetch]); + //useKeyboardSaveShortcut(form.submit); const handleFinish = async (values) => { From 282dbd09130e8d4b9b3119455d0618df088802db Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 1 Apr 2024 20:20:01 -0400 Subject: [PATCH 13/75] - Progress Commit Signed-off-by: Dave Richer --- .../components/header/header.component.jsx | 7 +- client/src/graphql/tasks.queries.js | 80 +++++++++---------- .../pages/manage/manage.page.component.jsx | 15 +++- .../src/pages/tasks/allTasksPageContainer.jsx | 48 +++++++++++ ...container.jsx => myTasksPageContainer.jsx} | 15 ++-- client/src/pages/tasks/taskPageTypes.jsx | 6 ++ .../src/pages/tasks/tasks.page.component.jsx | 22 +++-- client/src/translations/en_us/common.json | 5 ++ client/src/translations/es/common.json | 5 ++ client/src/translations/fr/common.json | 5 ++ 10 files changed, 148 insertions(+), 60 deletions(-) create mode 100644 client/src/pages/tasks/allTasksPageContainer.jsx rename client/src/pages/tasks/{tasks.page.container.jsx => myTasksPageContainer.jsx} (73%) create mode 100644 client/src/pages/tasks/taskPageTypes.jsx diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 5b53c37ee..40e2d10ff 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -464,7 +464,12 @@ function Header({ { key: 'mytasks', icon: , - label: {t('menus.header.my_tasks')}, + label: {t('menus.header.my_tasks')}, + }, + { + key: 'all_tasks', + icon: , + label: {t('menus.header.all_tasks')}, } ] }, diff --git a/client/src/graphql/tasks.queries.js b/client/src/graphql/tasks.queries.js index 615a04963..0492dbf07 100644 --- a/client/src/graphql/tasks.queries.js +++ b/client/src/graphql/tasks.queries.js @@ -1,46 +1,5 @@ import {gql} from "@apollo/client"; -/** - * All tasks paginated query - * @type {DocumentNode} - */ -export const QUERY_ALL_TASKS_PAGINATED = gql` - query QUERY_ALL_TASKS_PAGINATED( - $offset: Int - $limit: Int - $order: [tasks_order_by!]! - ) { - tasks( - offset: $offset - limit: $limit - order_by: $order - ) { - id - created_at - updated_at - title - description - deleted - deleted_at - due_date - created_by - assigned_to - completed - completed_at - remind_at - priority - jobid - joblineid - partsorderid - billid - } - tasks_aggregate { - aggregate { - count - } - } - } -`; const PARTIAL_TASK_FIELDS = gql` fragment TaskFields on tasks { @@ -87,6 +46,45 @@ const PARTIAL_TASK_FIELDS = gql` } `; +export const QUERY_ALL_TASKS_PAGINATED = gql` + ${PARTIAL_TASK_FIELDS} + query QUERY_ALL_TASKS_PAGINATED( + $offset: Int + $limit: Int + $bodyshop: uuid! + $deleted: Boolean + $completed: Boolean + $assigned_to: String + $order: [tasks_order_by!]! + ) { + tasks( + offset: $offset + limit: $limit + order_by: $order + where: { + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + assigned_to: {_eq: $assigned_to}, + completed: {_eq: $completed} + } + ) { + ...TaskFields + } + tasks_aggregate( + where: { + bodyshopid: {_eq: $bodyshop}, + deleted: {_eq: $deleted}, + assigned_to: {_eq: $assigned_to}, + completed: {_eq: $completed} + } + ) { + aggregate { + count + } + } + } +`; + // Query for joblineid export const QUERY_JOBLINE_TASKS_PAGINATED = gql` ${PARTIAL_TASK_FIELDS} diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index 035970b7c..319762891 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -102,7 +102,9 @@ const Dms = lazy(() => import("../dms/dms.container")); const DmsPayables = lazy(() => import("../dms-payables/dms-payables.container")); const ManageRootPage = lazy(() => import("../manage-root/manage-root.page.container")); const TtApprovals = lazy(() => import("../tt-approvals/tt-approvals.page.container")); -const TasksPage = lazy(() => import("../tasks/tasks.page.container")); +const MyTasksPage = lazy(() => import("../tasks/myTasksPageContainer.jsx")); +const AllTasksPage = lazy(() => import("../tasks/allTasksPageContainer.jsx")); + const TaskUpsertModalContainer = lazy(() => import("../../components/task-upsert-modal/task-upsert-modal.container")); const { Content, Footer } = Layout; @@ -259,12 +261,19 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy } /> }> - + } /> + }> + + } + /> ({ + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), + setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), +}); + +export function MyTasksPageContainer({bodyshop, currentUser, setBreadcrumbs, setSelectedHeader, query}) { + const {t} = useTranslation(); + useEffect(() => { + document.title = t("titles.all_tasks", { + app: InstanceRenderManager({ + imex: '$t(titles.imexonline)', + rome: '$t(titles.romeonline)', + promanager: '$t(titles.promanager)' + }) + }); + setSelectedHeader("all_tasks"); + setBreadcrumbs([ + { + link: "/manage/alltasks", + label: t("titles.bc.all_tasks"), + },]); + }, [t, setBreadcrumbs, setSelectedHeader]); + + return ( + + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(MyTasksPageContainer); diff --git a/client/src/pages/tasks/tasks.page.container.jsx b/client/src/pages/tasks/myTasksPageContainer.jsx similarity index 73% rename from client/src/pages/tasks/tasks.page.container.jsx rename to client/src/pages/tasks/myTasksPageContainer.jsx index c4151f1c6..94c3e07cd 100644 --- a/client/src/pages/tasks/tasks.page.container.jsx +++ b/client/src/pages/tasks/myTasksPageContainer.jsx @@ -7,6 +7,7 @@ import {createStructuredSelector} from "reselect"; import {setBreadcrumbs, setSelectedHeader} from "../../redux/application/application.actions"; import InstanceRenderManager from "../../utils/instanceRenderMgr.js"; import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors.js"; +import TaskPageTypes from "./taskPageTypes.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -18,30 +19,30 @@ const mapDispatchToProps = (dispatch) => ({ setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), }); -export function TasksPageContainer({bodyshop, currentUser, setBreadcrumbs, setSelectedHeader}) { +export function MyTasksPageContainer({bodyshop, currentUser, setBreadcrumbs, setSelectedHeader, query}) { const {t} = useTranslation(); useEffect(() => { - document.title = t("titles.tasks", { + document.title = t("titles.my_tasks", { app: InstanceRenderManager({ imex: '$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)' }) }); - setSelectedHeader("tasks"); + setSelectedHeader("my_tasks"); setBreadcrumbs([ { - link: "/manage/tasks", - label: t("titles.bc.tasks"), + link: "/manage/mytasks", + label: t("titles.bc.my_tasks"), },]); }, [t, setBreadcrumbs, setSelectedHeader]); return ( - + ); } export default connect( mapStateToProps, mapDispatchToProps -)(TasksPageContainer); +)(MyTasksPageContainer); diff --git a/client/src/pages/tasks/taskPageTypes.jsx b/client/src/pages/tasks/taskPageTypes.jsx new file mode 100644 index 000000000..5e85c9602 --- /dev/null +++ b/client/src/pages/tasks/taskPageTypes.jsx @@ -0,0 +1,6 @@ +export const TaskPageTypes = { + MY_TASKS: 'myTasks', + ALL_TASKS: 'allTasks', +}; + +export default TaskPageTypes; diff --git a/client/src/pages/tasks/tasks.page.component.jsx b/client/src/pages/tasks/tasks.page.component.jsx index b3758f48a..299a8e216 100644 --- a/client/src/pages/tasks/tasks.page.component.jsx +++ b/client/src/pages/tasks/tasks.page.component.jsx @@ -1,13 +1,19 @@ import React from "react"; import TaskListContainer from "../../components/task-list/task-list.container.jsx"; -import {QUERY_MY_TASKS_PAGINATED} from "../../graphql/tasks.queries.js"; +import {QUERY_ALL_TASKS_PAGINATED, QUERY_MY_TASKS_PAGINATED} from "../../graphql/tasks.queries.js"; +import taskPageTypes from "./taskPageTypes.jsx"; -export default function TasksPageComponent({bodyshop, currentUser}) { +export default function TasksPageComponent({bodyshop, currentUser, type}) { + switch (type) { + case taskPageTypes.MY_TASKS: + return + case taskPageTypes.ALL_TASKS: + return + } - return ( -
- -
- ); } diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index d1c14f89b..74ad27d2c 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2177,6 +2177,7 @@ "create_task": "Create Task", "tasks": "Tasks", "my_tasks": "My Tasks", + "all_tasks": "All Tasks", "accounting": "Accounting", "accounting-payables": "Payables", "accounting-payments": "Payments", @@ -3134,12 +3135,16 @@ }, "titles": { "tasks": "Tasks", + "all_tasks": "All Tasks", + "my_tasks": "My Tasks", "accounting-payables": "Payables | {{app}}", "accounting-payments": "Payments | {{app}}", "accounting-receivables": "Receivables | {{app}}", "app": "", "bc": { "tasks": "Tasks", + "all_tasks": "All Tasks", + "my_tasks": "My Tasks", "accounting-payables": "Payables", "accounting-payments": "Payments", "accounting-receivables": "Receivables", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 8d412e06b..e10d38c23 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2175,6 +2175,7 @@ "header": { "create_task": "", "tasks": "", + "all_tasks": "", "my_tasks": "", "accounting": "", "accounting-payables": "", @@ -3133,12 +3134,16 @@ }, "titles": { "tasks": "", + "all_tasks": "", + "my_tasks": "", "accounting-payables": "", "accounting-payments": "", "accounting-receivables": "", "app": "", "bc": { "tasks": "", + "all_tasks": "", + "my_tasks": "", "accounting-payables": "", "accounting-payments": "", "accounting-receivables": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index e39928eb3..e7f99b572 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2176,6 +2176,7 @@ "create_task": "", "tasks": "", "my_tasks": "", + "all_tasks": "", "accounting": "", "accounting-payables": "", "accounting-payments": "", @@ -3137,8 +3138,12 @@ "accounting-receivables": "", "app": "", "tasks": "", + "all_tasks": "", + "my_tasks": "", "bc": { "tasks": "", + "all_tasks": "", + "my_tasks": "", "accounting-payables": "", "accounting-payments": "", "accounting-receivables": "", From 90814f41a2b14d4d40eb21baa60850fee21692e9 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 2 Apr 2024 15:01:51 -0400 Subject: [PATCH 14/75] - Progress Commit Signed-off-by: Dave Richer --- .../jobs-list/jobs-list.component.jsx | 5 - .../task-list/task-list.component.jsx | 105 +++++++++++++----- .../task-list/task-list.container.jsx | 1 + .../task-upsert-modal.component.jsx | 2 +- client/src/graphql/tasks.queries.js | 18 +++ .../src/pages/tasks/allTasksPageContainer.jsx | 2 +- .../src/pages/tasks/myTasksPageContainer.jsx | 2 +- .../src/pages/tasks/tasks.page.component.jsx | 2 +- client/src/translations/en_us/common.json | 4 + client/src/translations/es/common.json | 7 +- client/src/translations/fr/common.json | 7 +- 11 files changed, 116 insertions(+), 39 deletions(-) diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index e952d8fda..591bcae8b 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -176,11 +176,6 @@ export function JobsList({ bodyshop, setJoyRideSteps }) { [], onFilter: (value, record) => value.includes(record.status) }, - { - title: t('jobs.fields.active_tasks'), - key: 'activetasks', - render: (text, record) => {record.tasks_aggregate.aggregate.count}, - }, { title: t("jobs.fields.vehicle"), dataIndex: "vehicle", diff --git a/client/src/components/task-list/task-list.component.jsx b/client/src/components/task-list/task-list.component.jsx index a331612b7..7f2b5d75d 100644 --- a/client/src/components/task-list/task-list.component.jsx +++ b/client/src/components/task-list/task-list.component.jsx @@ -26,20 +26,17 @@ import {setModalContext} from '../../redux/modals/modals.actions'; * @constructor */ const DueDateRecord = ({dueDate}) => { - if (dueDate) { - const dueDateDayjs = dayjs(dueDate); - const relativeDueDate = dueDateDayjs.fromNow(); - const today = dayjs(); + if (!dueDate) return <>; - if (dueDateDayjs.isBefore(today)) { - return
- {dueDate}
; - } else { - return
{dueDate}
; - } - } else { - return
N/A
; - } + const dueDateDayjs = dayjs(dueDate); + const relativeDueDate = dueDateDayjs.fromNow(); + const isBeforeToday = dueDateDayjs.isBefore(dayjs()); + + return ( +
+ {dueDate} +
+ ); } /** @@ -81,6 +78,7 @@ const mapStateToProps = (state) => ({ export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent); function TaskListComponent({ + bodyshop, loading, tasks, total, @@ -117,9 +115,13 @@ function TaskListComponent({ title: t("tasks.fields.assigned_to"), dataIndex: "assigned_to", key: "assigned_to", - width: '10%', + width: '8%', sorter: true, sortOrder: sortcolumn === "assigned_to" && sortorder, + render: (text, record) => { + const employee = bodyshop?.employees?.find(e => e.user_email === record.assigned_to); + return employee ? `${ employee.first_name} ${ employee.last_name}` : t("general.labels.na"); + } } ); } @@ -129,12 +131,46 @@ function TaskListComponent({ title: t("tasks.fields.job.ro_number"), dataIndex: ["job", "ro_number"], key: "job.ro_number", - width: '5%', - render: (text, record) => ( - - {record.job.ro_number} - - ), + width: '8%', + render: (text, record) => + record.job + ? {record.job.ro_number || t("general.labels.na")} + : t("general.labels.na") + }, + { + title: t("tasks.fields.jobline"), + dataIndex: ["jobline", "id"], + key: "jobline.id", + width: '8%', + render: (text, record) => record?.jobline?.line_desc || '' + }, + { + title: t("tasks.fields.parts_order"), + dataIndex: ["parts_order", "id"], + key: "part_order.id", + width: '8%', + render: (text, record) => + record.parts_order + ? + {record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name + ? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}` + : t("general.labels.na")} + + : '' + }, + { + title: t("tasks.fields.bill"), + dataIndex: ["bill", "id"], + key: "bill.id", + width: '8%', + render: (text, record) => + record.bill + ? + {record.bill.invoice_number && record.bill.vendor && record.bill.vendor.name + ? `${record.bill.invoice_number} - ${record.bill.vendor.name}` + : t("general.labels.na")} + + : '' }, { title: t("tasks.fields.title"), @@ -143,13 +179,6 @@ function TaskListComponent({ sorter: true, sortOrder: sortcolumn === "title" && sortorder, }, - { - title: t("tasks.fields.description"), - dataIndex: "description", - key: "description", - sorter: true, - sortOrder: sortcolumn === "description" && sortorder, - }, { title: t("tasks.fields.due_date"), dataIndex: "due_date", @@ -159,6 +188,15 @@ function TaskListComponent({ width: '8%', render: (text, record) => , }, + { + title: t("tasks.fields.remind_at"), + dataIndex: "remind_at", + key: "remind_at", + sorter: true, + sortOrder: sortcolumn === "remind_at" && sortorder, + width: '8%', + render: (text, record) => , + }, { title: t("tasks.fields.priority"), dataIndex: "priority", @@ -229,6 +267,12 @@ function TaskListComponent({ } history({search: queryString.stringify(search)}); }; + + const expandableRow = (record) => { + return + {record.description} + + }; /** * Extra actions for the tasks @@ -279,16 +323,21 @@ function TaskListComponent({
record.description, + }} /> ); diff --git a/client/src/components/task-list/task-list.container.jsx b/client/src/components/task-list/task-list.container.jsx index d2b24ffcb..191b8a8b1 100644 --- a/client/src/components/task-list/task-list.container.jsx +++ b/client/src/components/task-list/task-list.container.jsx @@ -136,6 +136,7 @@ export default function TaskListContainer({bodyshop, titleTranslation ,query, r return (
+ +
+
+ + +
 
+ + + +
+ + +
${strings.header}

${strings.subHeader}

+ + + + +
+ +
${strings.body}
+ + + + + + + + + + +
 
+
+
+ +` +} + +module.exports = generateEmailTemplate; diff --git a/server/email/sendemail.js b/server/email/sendemail.js index cface107f..a976cee7d 100644 --- a/server/email/sendemail.js +++ b/server/email/sendemail.js @@ -10,6 +10,8 @@ const InstanceManager = require("../utils/instanceMgr").default; const logger = require("../utils/logger"); const client = require("../graphql-client/graphql-client").client; const queries = require("../graphql-client/queries"); +const {isObject} = require("lodash"); +const generateEmailTemplate = require('./generateTemplate'); const ses = new aws.SES({ // The key apiVersion is no longer supported in v3, and can be removed. @@ -88,7 +90,8 @@ exports.sendEmail = async (req, res) => { replyTo: req.body.ReplyTo.Email, to: req.body.to, cc: req.body.cc, - subject: req.body.subject + subject: req.body.subject, + templateStrings: req.body.templateStrings }); let downloadedMedia = []; @@ -104,6 +107,7 @@ exports.sendEmail = async (req, res) => { to: req.body.to, cc: req.body.cc, subject: req.body.subject, + templateStrings: req.body.templateStrings, error }); } @@ -134,7 +138,7 @@ exports.sendEmail = async (req, res) => { }; }) ] || null, - html: req.body.html, + html: isObject(req.body?.templateStrings) ? generateEmailTemplate(req.body.templateStrings) : req.body.html, ses: { // optional extra arguments for SendRawEmail Tags: [ @@ -153,7 +157,8 @@ exports.sendEmail = async (req, res) => { replyTo: req.body.ReplyTo.Email, to: req.body.to, cc: req.body.cc, - subject: req.body.subject + subject: req.body.subject, + templateStrings: req.body.templateStrings // info, }); logEmail(req, { @@ -172,6 +177,7 @@ exports.sendEmail = async (req, res) => { to: req.body.to, cc: req.body.cc, subject: req.body.subject, + templateStrings: req.body.templateStrings, error: err }); logEmail(req, { From 2b172f99997edeffabae740c4a5e40e18b64e9e1 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 2 Apr 2024 21:54:07 -0400 Subject: [PATCH 18/75] - fix Signed-off-by: Dave Richer --- .../task-upsert-modal/task-upsert-modal.container.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx index cb7f03cf3..cd8cd5aee 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx @@ -18,7 +18,6 @@ import {replaceUndefinedWithNull} from "../../utils/undefinedtonull.js"; import {useNavigate} from "react-router-dom"; import axios from "axios"; import dayjs from '../../utils/day'; -import {insertAuditTrail} from "../../redux/application/application.actions.js"; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, @@ -27,7 +26,6 @@ const mapStateToProps = createStructuredSelector({ }); const mapDispatchToProps = (dispatch) => ({ toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")), - insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); export function TaskUpsertModalContainer({ @@ -35,7 +33,6 @@ export function TaskUpsertModalContainer({ currentUser, taskUpsert, toggleModalVisible, - insertAuditTrail }) { const {t} = useTranslation(); const history = useNavigate(); From 3eb010285da4b843813e11151cbafc4226e129b2 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 3 Apr 2024 13:01:56 -0400 Subject: [PATCH 19/75] - Update date picker presets Signed-off-by: Dave Richer --- .../task-upsert-modal/task-upsert-modal.component.jsx | 4 ++++ client/src/translations/en_us/common.json | 5 ++++- client/src/translations/es/common.json | 5 ++++- client/src/translations/fr/common.json | 5 ++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx index 642e18299..853c89825 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx @@ -36,10 +36,14 @@ export function TaskUpsertModalComponent({ const {t} = useTranslation(); const datePickerPresets = [ + {label: t('tasks.date_presets.today'), value: dayjs()}, + {label: t('tasks.date_presets.tomorrow'), value: dayjs().add(1, 'day')}, {label: t('tasks.date_presets.next_week'), value: dayjs().add(1, 'week')}, {label: t('tasks.date_presets.two_weeks'), value: dayjs().add(2, 'weeks')}, {label: t('tasks.date_presets.three_weeks'), value: dayjs().add(3, 'weeks')}, {label: t('tasks.date_presets.one_month'), value: dayjs().add(1, 'month')}, + {label: t('tasks.date_presets.three_months'), value: dayjs().add(3, 'month')}, + ]; const clearRelations = () => { diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 960479972..db650382e 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2116,7 +2116,10 @@ "next_week": "Next Week", "two_weeks": "Two Weeks", "three_weeks": "Three Weeks", - "one_month": "One Month" + "one_month": "One Month", + "today": "Today", + "tomorrow": "Tomorrow", + "three_months": "Three Months" }, "actions": { "new": "New Task", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 5221925dc..c442aeebc 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2116,7 +2116,10 @@ "next_week": "", "two_weeks": "", "three_weeks": "", - "one_month": "" + "one_month": "", + "today": "", + "tomorrow": "", + "three_months": "" }, "actions": { "new": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 366a33369..019790f3b 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2116,7 +2116,10 @@ "next_week": "", "two_weeks": "", "three_weeks": "", - "one_month": "" + "one_month": "", + "today": "", + "tomorrow": "", + "three_months": "" }, "actions": { "new": "", From e51f72ff983f4992add335196ed1ed2a5bf0affd Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 3 Apr 2024 13:47:40 -0400 Subject: [PATCH 20/75] - its sign, not sing :D Signed-off-by: Dave Richer --- .../task-upsert-modal/task-upsert-modal.container.jsx | 4 ++-- server/email/generateTemplate.js | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx index cd8cd5aee..5d13901c8 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.container.jsx @@ -144,7 +144,7 @@ export function TaskUpsertModalContainer({ templateStrings: { header: values.title, subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format('MM/DD/YYYY')}` : ''}`, - body: `Please sing in to your account to view the task details.` + body: `Please sign in to your account to view the task details.` } }).catch(e => console.error(`Something went wrong sending email to Assigned party on Task creation. ${e.message || ''}`)); } @@ -211,7 +211,7 @@ export function TaskUpsertModalContainer({ templateStrings: { header: values.title, subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format('MM/DD/YYYY')}` : ''}`, - body: `Please sing in to your account to view the task details.` + body: `Please sign to your account to view the task details.` } }).catch(e => console.error(`Something went wrong sending email to Assigned party on Task edit. ${e.message || ''}`)); diff --git a/server/email/generateTemplate.js b/server/email/generateTemplate.js index eac8a4a55..28a3f7d2e 100644 --- a/server/email/generateTemplate.js +++ b/server/email/generateTemplate.js @@ -9,6 +9,12 @@ const moment = require("moment"); // - footer - The footer of the email // - dateLine - The date line of the email +// Google cloud scheduler / Hasura Cron Jobs (Preferred) +// Reminders every x mins +// Event Trigger + +// TODO use InstanceDynamicManager on the footer default to prevent the word IMEX from being hardcoded + const generateEmailTemplate = (strings) => { let now = () =>moment().format('MM/DD/YYYY @ hh:mm a'); From ab031c01ded6a76795b60ab4c5c9353c9593ba3f Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 3 Apr 2024 14:09:09 -0400 Subject: [PATCH 21/75] - reapply proper prettier formatting. Signed-off-by: Dave Richer --- .../bills-list-table.component.jsx | 43 +- .../components/header/header.component.jsx | 329 +- .../job-lines-expander.component.jsx | 145 +- .../job-detail-lines/job-lines.component.jsx | 4 +- .../job-line-convert-to-labor.component.jsx | 2 +- .../jobs-detail-header-actions.component.jsx | 302 +- .../jobs-list-paginated.component.jsx | 60 +- .../jobs-list/jobs-list.component.jsx | 91 +- .../parts-order-list-table.component.jsx | 98 +- .../parts-order-modal.container.jsx | 2 +- .../task-list/task-list.component.jsx | 30 +- .../task-list/task-list.container.jsx | 23 +- .../task-upsert-modal.container.jsx | 11 +- client/src/graphql/bills.queries.js | 2 +- client/src/graphql/jobs.queries.js | 14 +- client/src/graphql/tasks.queries.js | 46 +- .../jobs-detail.page.component.jsx | 20 +- .../pages/manage/manage.page.component.jsx | 261 +- .../src/pages/tasks/allTasksPageContainer.jsx | 3 +- .../src/pages/tasks/myTasksPageContainer.jsx | 3 +- client/src/pages/tasks/taskPageTypes.jsx | 4 +- client/src/translations/en_us/common.json | 6608 ++++++++-------- client/src/translations/es/common.json | 6620 ++++++++--------- client/src/translations/fr/common.json | 6616 ++++++++-------- server/email/generateTemplate.js | 4 +- server/email/sendemail.js | 34 +- 26 files changed, 10734 insertions(+), 10641 deletions(-) diff --git a/client/src/components/bills-list-table/bills-list-table.component.jsx b/client/src/components/bills-list-table/bills-list-table.component.jsx index b7ac48060..cb88f46b8 100644 --- a/client/src/components/bills-list-table/bills-list-table.component.jsx +++ b/client/src/components/bills-list-table/bills-list-table.component.jsx @@ -6,7 +6,7 @@ import {connect} from "react-redux"; import {createStructuredSelector} from "reselect"; import {selectJobReadOnly} from "../../redux/application/application.selectors"; import {setModalContext} from "../../redux/modals/modals.actions"; -import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; +import {selectBodyshop} from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import {DateFormatter} from "../../utils/DateFormatter"; import {alphaSort, dateSort} from "../../utils/sorters"; @@ -22,7 +22,10 @@ const mapStateToProps = createStructuredSelector({ }); const mapDispatchToProps = (dispatch) => ({ - setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })), + setBillEnterContext: (context) => dispatch(setModalContext({ + context: context, + modal: "billEnter" + })), setReconciliationContext: (context) => dispatch(setModalContext({ context: context, modal: "reconciliation" @@ -40,7 +43,7 @@ export function BillsListTableComponent({ setReconciliationContext, setTaskUpsertContext, }) { - const { t } = useTranslation(); + const {t} = useTranslation(); const [state, setState] = useState({ sortedInfo: {} @@ -51,14 +54,14 @@ export function BillsListTableComponent({ const Templates = TemplateList("bill"); const bills = billsQuery.data ? billsQuery.data.bills : []; - const { refetch } = billsQuery; + const {refetch} = billsQuery; const recordActions = (record, showView = false) => ( {showView && ( )} - + @@ -81,9 +84,9 @@ export function BillsListTableComponent({ )} @@ -126,7 +129,7 @@ export function BillsListTableComponent({ key: "is_credit_memo", sorter: (a, b) => a.is_credit_memo - b.is_credit_memo, sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order, - render: (text, record) => + render: (text, record) => }, { title: t("bills.fields.exported"), @@ -134,7 +137,7 @@ export function BillsListTableComponent({ key: "exported", sorter: (a, b) => a.exported - b.exported, sortOrder: state.sortedInfo.columnKey === "exported" && state.sortedInfo.order, - render: (text, record) => + render: (text, record) => }, { title: t("general.labels.actions"), @@ -145,18 +148,18 @@ export function BillsListTableComponent({ ]; const handleTableChange = (pagination, filters, sorter) => { - setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + setState({...state, filteredInfo: filters, sortedInfo: sorter}); }; const filteredBills = bills ? searchText === "" ? bills : bills.filter( - (b) => - (b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) || - (b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) || - (b.total || "").toString().toLowerCase().includes(searchText.toLowerCase()) - ) + (b) => + (b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) || + (b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) || + (b.total || "").toString().toLowerCase().includes(searchText.toLowerCase()) + ) : []; return ( @@ -165,14 +168,14 @@ export function BillsListTableComponent({ extra={ {job && job.converted ? ( <> )} - - + )} @@ -129,7 +137,7 @@ export function BillsListTableComponent({ key: "is_credit_memo", sorter: (a, b) => a.is_credit_memo - b.is_credit_memo, sortOrder: state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.order, - render: (text, record) => + render: (text, record) => }, { title: t("bills.fields.exported"), @@ -137,7 +145,7 @@ export function BillsListTableComponent({ key: "exported", sorter: (a, b) => a.exported - b.exported, sortOrder: state.sortedInfo.columnKey === "exported" && state.sortedInfo.order, - render: (text, record) => + render: (text, record) => }, { title: t("general.labels.actions"), @@ -148,18 +156,18 @@ export function BillsListTableComponent({ ]; const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); }; const filteredBills = bills ? searchText === "" ? bills : bills.filter( - (b) => - (b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) || - (b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) || - (b.total || "").toString().toLowerCase().includes(searchText.toLowerCase()) - ) + (b) => + (b.invoice_number || "").toLowerCase().includes(searchText.toLowerCase()) || + (b.vendor.name || "").toLowerCase().includes(searchText.toLowerCase()) || + (b.total || "").toString().toLowerCase().includes(searchText.toLowerCase()) + ) : []; return ( @@ -168,14 +176,14 @@ export function BillsListTableComponent({ extra={ {job && job.converted ? ( <> - - - + + - - + + @@ -283,10 +277,10 @@ export function JobsDetailPage({ return (
- - - - + + + +
- - - - + + + + history({search: `?tab=${key}`})} - tabBarStyle={{fontWeight: "bold", borderBottom: "10px"}} + onChange={(key) => history({ search: `?tab=${key}` })} + tabBarStyle={{ fontWeight: "bold", borderBottom: "10px" }} items={[ { key: "general", - icon: , + icon: , label: t("menus.jobsdetail.general"), forceRender: true, - children: + children: }, { key: "repairdata", - icon: , + icon: , label: t("menus.jobsdetail.repairdata"), forceRender: true, - children: + children: }, { key: "rates", - icon: , + icon: , label: t("menus.jobsdetail.rates"), forceRender: true, - children: + children: }, { key: "totals", - icon: , + icon: , label: t("menus.jobsdetail.totals"), - children: + children: }, { key: "partssublet", - icon: , - label: HasFeatureAccess({featureName: "bills", bodyshop}) + icon: , + label: HasFeatureAccess({ featureName: "bills", bodyshop }) ? t("menus.jobsdetail.partssublet") : t("menus.jobsdetail.parts"), - children: + children: }, ...(InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({featureName: "timetickets", bodyshop}) + promanager: HasFeatureAccess({ featureName: "timetickets", bodyshop }) }) ? [ - { - key: "labor", - icon: , - label: t("menus.jobsdetail.labor"), - children: - } - ] + { + key: "labor", + icon: , + label: t("menus.jobsdetail.labor"), + children: + } + ] : []), { key: "lifecycle", - icon: , + icon: , label: t("menus.jobsdetail.lifecycle"), - children: + children: }, { key: "dates", - icon: , + icon: , label: t("menus.jobsdetail.dates"), forceRender: true, - children: + children: }, ...(InstanceRenderManager({ imex: true, rome: true, - promanager: HasFeatureAccess({featureName: "media", bodyshop}) + promanager: HasFeatureAccess({ featureName: "media", bodyshop }) }) ? [ - { - key: "documents", - icon: , - label: t("jobs.labels.documents"), - children: bodyshop.uselocalmediaserver ? ( - - ) : ( - - ) - } - ] + { + key: "documents", + icon: , + label: t("jobs.labels.documents"), + children: bodyshop.uselocalmediaserver ? ( + + ) : ( + + ) + } + ] : []), { key: "notes", - icon: , + icon: , label: t("jobs.labels.notes"), - children: + children: }, { key: "audit", - icon: , + icon: , label: t("jobs.labels.audit"), - children: + children: }, { - key: 'tasks', - icon: , - label: - {t("jobs.labels.tasks")}{job.tasks_aggregate.aggregate.count > 0 && - } - , - children: - }, + key: "tasks", + icon: , + label: ( + + {t("jobs.labels.tasks")} + {job.tasks_aggregate.aggregate.count > 0 && } + + ), + children: ( + + ) + } ]} /> @@ -424,7 +426,7 @@ export function JobsDetailPage({ export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage); const transformJobToForm = (job) => { - const transformedJob = {...job}; + const transformedJob = { ...job }; transformedJob.parts_tax_rates = Object.keys(transformedJob.parts_tax_rates).reduce((acc, parttype) => { acc[parttype] = Object.keys(transformedJob.parts_tax_rates[parttype]).reduce((innerAcc, key) => { diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index 476642332..d0f5ffeb4 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -1,10 +1,10 @@ -import {Button, Collapse, FloatButton, Layout, Space, Spin, Tag} from "antd"; +import { Button, Collapse, FloatButton, Layout, Space, Spin, Tag } from "antd"; // import preval from "preval.macro"; -import React, {lazy, Suspense, useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; -import {connect} from "react-redux"; -import {Link, Route, Routes} from "react-router-dom"; -import {createStructuredSelector} from "reselect"; +import React, { lazy, Suspense, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, Route, Routes } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component"; import ChatAffixContainer from "../../components/chat-affix/chat-affix.container"; import ConflictComponent from "../../components/conflict/conflict.component"; @@ -17,26 +17,21 @@ import TestComponent from "../../components/_test/test.page"; import HeaderContainer from "../../components/header/header.container"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import PartnerPingComponent from "../../components/partner-ping/partner-ping.component"; -import PrintCenterModalContainer - from "../../components/print-center-modal/print-center-modal.container"; +import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component"; -import {requestForToken} from "../../firebase/firebase.utils"; -import {selectBodyshop, selectInstanceConflict} from "../../redux/user/user.selectors"; +import { requestForToken } from "../../firebase/firebase.utils"; +import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors"; import UpdateAlert from "../../components/update-alert/update-alert.component"; -import {setJoyRideFinished} from "../../redux/application/application.actions.js"; -import { - selectEnableJoyRide, - selectJoyRideSteps -} from "../../redux/application/application.selectors.js"; +import { setJoyRideFinished } from "../../redux/application/application.actions.js"; +import { selectEnableJoyRide, selectJoyRideSteps } from "../../redux/application/application.selectors.js"; import InstanceRenderManager from "../../utils/instanceRenderMgr.js"; import "./manage.page.styles.scss"; - const JobsPage = lazy(() => import("../jobs/jobs.page")); -const CardPaymentModalContainer = lazy(() => - import("../../components/card-payment-modal/card-payment-modal.container.") +const CardPaymentModalContainer = lazy( + () => import("../../components/card-payment-modal/card-payment-modal.container.") ); const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container")); @@ -64,8 +59,8 @@ const JobCostingModal = lazy(() => import("../../components/job-costing-modal/jo const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container")); const BillEnterModalContainer = lazy(() => import("../../components/bill-enter-modal/bill-enter-modal.container")); const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container")); -const TimeTicketModalTask = lazy(() => - import("../../components/time-ticket-task-modal/time-ticket-task-modal.container") +const TimeTicketModalTask = lazy( + () => import("../../components/time-ticket-task-modal/time-ticket-task-modal.container") ); const PaymentModalContainer = lazy(() => import("../../components/payment-modal/payment-modal.container")); const ProductionListPage = lazy(() => import("../production-list/production-list.container")); @@ -106,7 +101,7 @@ const MyTasksPage = lazy(() => import("../tasks/myTasksPageContainer.jsx")); const AllTasksPage = lazy(() => import("../tasks/allTasksPageContainer.jsx")); const TaskUpsertModalContainer = lazy(() => import("../../components/task-upsert-modal/task-upsert-modal.container")); -const {Content, Footer} = Layout; +const { Content, Footer } = Layout; const mapStateToProps = createStructuredSelector({ conflict: selectInstanceConflict, @@ -119,8 +114,8 @@ const mapDispatchToProps = (dispatch) => ({ setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps)) }); -export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished}) { - const {t} = useTranslation(); +export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) { + const { t } = useTranslation(); const [chatVisible] = useState(false); const [tours, setTours] = useState([]); @@ -156,264 +151,266 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR /> } > - + - - - - - - - - - - + + + + + + + + + + - }/> - }/> + } /> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - - } + }> + + + } /> }> - - } + }> + + + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> @@ -426,16 +423,16 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR }> - + }> + } /> }> - + }> + } /> @@ -443,8 +440,8 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR }> - + }> + } /> @@ -452,56 +449,56 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> @@ -509,65 +506,65 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> - }/> + } /> }> - + }> + } /> }> - + }> + } /> }> - + }> + } /> @@ -577,16 +574,16 @@ export function Manage({conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyR let PageContent; - if (conflict) PageContent = ; - else if (bodyshop && bodyshop.sub_status !== "active") PageContent = ; + if (conflict) PageContent = ; + else if (bodyshop && bodyshop.sub_status !== "active") PageContent = ; else PageContent = AppRouteTable; return ( <> - - - - + + + + - - } showDialog> + + } showDialog> {PageContent} - +