From 38aef71269b0347072156a2650133fb53115770a Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 20 Mar 2024 15:11:08 -0400 Subject: [PATCH] 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);