From 32bdea559e4da5722ee923419a904558f2053e49 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 9 Jul 2025 14:37:51 -0700 Subject: [PATCH] IO-3296 Schedule Delivery Dashboard Signed-off-by: Allan Carr --- .../scheduled-delivery-today.component.jsx | 411 ++++++++++++++++++ .../scheduled-in-today.component.jsx | 4 +- .../scheduled-out-today.component.jsx | 4 +- .../dashboard-grid/componentList.js | 46 +- client/src/translations/en_us/common.json | 2 + client/src/translations/es/common.json | 2 + client/src/translations/fr/common.json | 2 + 7 files changed, 450 insertions(+), 21 deletions(-) create mode 100644 client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx diff --git a/client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx b/client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx new file mode 100644 index 000000000..8728de179 --- /dev/null +++ b/client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx @@ -0,0 +1,411 @@ +import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons"; +import { Card, Space, Switch, Table, Tooltip, Typography } from "antd"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { TimeFormatter } from "../../../utils/DateFormatter"; +import { onlyUnique } from "../../../utils/arrayHelper"; +import dayjs from "../../../utils/day"; +import { alphaSort, dateSort } from "../../../utils/sorters"; +import useLocalStorage from "../../../utils/useLocalStorage"; +import ChatOpenButton from "../../chat-open-button/chat-open-button.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../../owner-name-display/owner-name-display.component"; +import DashboardRefreshRequired from "../refresh-required.component"; + +export default function DashboardScheduledDeliveryToday({ data, ...cardProps }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {} + }); + const [isTvModeScheduledDelivery, setIsTvModeScheduledDelivery] = useLocalStorage("isTvModeScheduledDelivery", false); + if (!data) return null; + if (!data.scheduled_delivery_today) return ; + + const scheduledDeliveryToday = data.scheduled_delivery_today.map((item) => { + const joblines_body = item.joblines + ? item.joblines.filter((l) => l.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0) + : 0; + const joblines_ref = item.joblines + ? item.joblines.filter((l) => l.mod_lbr_ty === "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0) + : 0; + return { + ...item, + joblines_body, + joblines_ref + }; + }); + + const tvFontSize = 18; + const tvFontWeight = "bold"; + + const tvColumns = [ + { + title: t("jobs.fields.scheduled_delivery"), + dataIndex: "scheduled_delivery", + key: "scheduled_delivery", + ellipsis: true, + sorter: (a, b) => dateSort(a.scheduled_delivery, b.scheduled_delivery), + sortOrder: state.sortedInfo.columnKey === "scheduled_delivery" && state.sortedInfo.order, + render: (text, record) => ( + + {record.scheduled_delivery} + + ) + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => ( + e.stopPropagation()}> + + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && } + {record.iouparent && ( + + + + )} + + + + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + return record.ownerid ? ( + e.stopPropagation()}> + + + + + ) : ( + + + + ); + } + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => + alphaSort( + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, + `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` + ), + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()}> + + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + + ) : ( + {`${ + record.v_model_yr || "" + } ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("appointments.fields.alt_transport"), + dataIndex: "alt_transport", + key: "alt_transport", + ellipsis: true, + sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), + sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order, + filters: + (scheduledDeliveryToday && + scheduledDeliveryToday + .map((j) => j.alt_transport) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Alt. Transport*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.alt_transport), + render: (text, record) => ( + {record.alt_transport} + ) + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + ellipsis: true, + sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filters: + (scheduledDeliveryToday && + scheduledDeliveryToday + .map((j) => j.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.status), + render: (text, record) => {record.status} + }, + { + title: t("jobs.fields.lab"), + dataIndex: "joblines_body", + key: "joblines_body", + sorter: (a, b) => a.joblines_body - b.joblines_body, + sortOrder: state.sortedInfo.columnKey === "joblines_body" && state.sortedInfo.order, + align: "right", + render: (text, record) => ( + {record.joblines_body.toFixed(1)} + ) + }, + { + title: t("jobs.fields.lar"), + dataIndex: "joblines_ref", + key: "joblines_ref", + sorter: (a, b) => a.joblines_ref - b.joblines_ref, + sortOrder: state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order, + align: "right", + render: (text, record) => ( + {record.joblines_ref.toFixed(1)} + ) + } + ]; + + const columns = [ + { + title: t("jobs.fields.scheduled_delivery"), + dataIndex: "scheduled_delivery", + key: "scheduled_delivery", + ellipsis: true, + sorter: (a, b) => dateSort(a.scheduled_delivery, b.scheduled_delivery), + sortOrder: state.sortedInfo.columnKey === "scheduled_delivery" && state.sortedInfo.order, + render: (text, record) => {record.scheduled_delivery} + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => ( + e.stopPropagation()}> + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && } + {record.iouparent && ( + + + + )} + + + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + return record.ownerid ? ( + e.stopPropagation()}> + + + ) : ( + + + + ); + } + }, + { + title: t("dashboard.labels.phone"), + dataIndex: "ownr_ph", + key: "ownr_ph", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + + + + + ) + }, + { + title: t("jobs.fields.ownr_ea"), + dataIndex: "ownr_ea", + key: "ownr_ea", + ellipsis: true, + responsive: ["md"], + render: (text, record) => {record.ownr_ea} + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => + alphaSort( + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, + `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` + ), + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()}> + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true, + responsive: ["md"], + sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), + sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, + filters: + (scheduledDeliveryToday && + scheduledDeliveryToday + .map((j) => j.ins_co_nm) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Ins. Co.*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.ins_co_nm) + }, + { + title: t("appointments.fields.alt_transport"), + dataIndex: "alt_transport", + key: "alt_transport", + ellipsis: true, + sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), + sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order, + filters: + (scheduledDeliveryToday && + scheduledDeliveryToday + .map((j) => j.alt_transport) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Alt. Transport*", + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.alt_transport) + } + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + return ( + + {t("general.labels.tvmode")} + setIsTvModeScheduledDelivery(!isTvModeScheduledDelivery)} + defaultChecked={isTvModeScheduledDelivery} + /> + + } + {...cardProps} + > +
+ + + + ); +} + +export const DashboardScheduledDeliveryTodayGql = ` + scheduled_delivery_today: jobs(where: { + date_invoiced: {_is_null: true}, + ro_number: {_is_null: false}, + voided: {_eq: false}, + scheduled_delivery: {_gte: "${dayjs().startOf("day").toISOString()}", + _lte: "${dayjs().endOf("day").toISOString()}"}}) { + alt_transport + clm_no + jobid: id + joblines(where: {removed: {_eq: false}}) { + mod_lb_hrs + mod_lbr_ty + } + ins_co_nm + iouparent + ownerid + ownr_co_nm + ownr_ea + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph2 + production_vars + ro_number + scheduled_delivery + status + suspended + v_make_desc + v_model_desc + v_model_yr + v_vin + vehicleid + } +`; diff --git a/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx b/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx index d3caeaca2..7fc1be35d 100644 --- a/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx +++ b/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx @@ -1,11 +1,11 @@ import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons"; import { Card, Space, Switch, Table, Tooltip, Typography } from "antd"; -import dayjs from "../../../utils/day"; -import React, { useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { TimeFormatter } from "../../../utils/DateFormatter"; import { onlyUnique } from "../../../utils/arrayHelper"; +import dayjs from "../../../utils/day"; import { alphaSort, dateSort } from "../../../utils/sorters"; import useLocalStorage from "../../../utils/useLocalStorage"; import ChatOpenButton from "../../chat-open-button/chat-open-button.component"; diff --git a/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx b/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx index f83e2df5d..da0871260 100644 --- a/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx +++ b/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx @@ -1,11 +1,11 @@ import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons"; import { Card, Space, Switch, Table, Tooltip, Typography } from "antd"; -import dayjs from "../../../utils/day"; -import React, { useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { TimeFormatter } from "../../../utils/DateFormatter"; import { onlyUnique } from "../../../utils/arrayHelper"; +import dayjs from "../../../utils/day"; import { alphaSort, dateSort } from "../../../utils/sorters"; import useLocalStorage from "../../../utils/useLocalStorage"; import ChatOpenButton from "../../chat-open-button/chat-open-button.component"; diff --git a/client/src/components/dashboard-grid/componentList.js b/client/src/components/dashboard-grid/componentList.js index 015d3509e..5bd866927 100644 --- a/client/src/components/dashboard-grid/componentList.js +++ b/client/src/components/dashboard-grid/componentList.js @@ -1,30 +1,33 @@ import i18next from "i18next"; -import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component.jsx"; -import { - DashboardTotalProductionHours, - DashboardTotalProductionHoursGql -} from "../dashboard-components/total-production-hours/total-production-hours.component.jsx"; -import DashboardProjectedMonthlySales, { - DashboardProjectedMonthlySalesGql -} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component.jsx"; -import DashboardMonthlyRevenueGraph, { - DashboardMonthlyRevenueGraphGql -} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component.jsx"; -import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx"; -import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component.jsx"; -import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component.jsx"; +import JobLifecycleDashboardComponent, { + JobLifecycleDashboardGQL +} from "../dashboard-components/job-lifecycle/job-lifecycle-dashboard.component.jsx"; import DashboardMonthlyEmployeeEfficiency, { DashboardMonthlyEmployeeEfficiencyGql } from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx"; +import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx"; +import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component.jsx"; +import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component.jsx"; +import DashboardMonthlyRevenueGraph, { + DashboardMonthlyRevenueGraphGql +} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component.jsx"; +import DashboardProjectedMonthlySales, { + DashboardProjectedMonthlySalesGql +} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component.jsx"; +import DashboardScheduledDeliveryToday, { + DashboardScheduledDeliveryTodayGql +} from "../dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx"; import DashboardScheduledInToday, { DashboardScheduledInTodayGql } from "../dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx"; import DashboardScheduledOutToday, { DashboardScheduledOutTodayGql } from "../dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx"; -import JobLifecycleDashboardComponent, { - JobLifecycleDashboardGQL -} from "../dashboard-components/job-lifecycle/job-lifecycle-dashboard.component.jsx"; +import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component.jsx"; +import { + DashboardTotalProductionHours, + DashboardTotalProductionHoursGql +} from "../dashboard-components/total-production-hours/total-production-hours.component.jsx"; const componentList = { ProductionDollars: { @@ -118,6 +121,15 @@ const componentList = { w: 10, h: 3 }, + ScheduleDeliveryToday: { + label: i18next.t("dashboard.titles.scheduleddeliverytoday"), + component: DashboardScheduledDeliveryToday, + gqlFragment: DashboardScheduledDeliveryTodayGql, + minW: 6, + minH: 2, + w: 10, + h: 3 + }, JobLifecycle: { label: i18next.t("dashboard.titles.joblifecycle"), component: JobLifecycleDashboardComponent, diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index b56a6e5ee..4e829ba4e 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -998,6 +998,8 @@ "productiondollars": "Total Dollars in Production", "productionhours": "Total Hours in Production", "projectedmonthlysales": "Projected Monthly Sales", + "scheduleddeliverydate": "Scheduled Delivery Date: {{date}}", + "scheduleddeliverytoday": "Scheduled Delivery Today", "scheduledindate": "Scheduled In Today: {{date}}", "scheduledintoday": "Scheduled In Today", "scheduledoutdate": "Scheduled Out Today: {{date}}", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 15c9cabbc..e5e31c56e 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -998,6 +998,8 @@ "productiondollars": "", "productionhours": "", "projectedmonthlysales": "", + "scheduleddeliverydate": "", + "scheduleddeliverytoday": "", "scheduledindate": "", "scheduledintoday": "", "scheduledoutdate": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 65c20f2da..d458240bc 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -998,6 +998,8 @@ "productiondollars": "", "productionhours": "", "projectedmonthlysales": "", + "scheduleddeliverydate": "", + "scheduleddeliverytoday": "", "scheduledindate": "", "scheduledintoday": "", "scheduledoutdate": "",