From 914a7e3c7b16e75e89b07070474ea0710189cf51 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 16 Jun 2021 10:51:53 -0700 Subject: [PATCH 1/3] IO-306 Dashboard monthly employee efficiency --- bodyshop_translations.babel | 21 +++ .../monthly-employee-efficiency.component.jsx | 166 ++++++++++++++++++ .../total-production-hours.component.jsx | 12 +- .../dashboard-grid.component.jsx | 27 ++- .../components/header/header.component.jsx | 4 +- .../jobs-create-jobs-info.component.jsx | 22 ++- ...chedule-calendar-header-graph.component.js | 2 +- .../jobs-create/jobs-create.container.jsx | 3 + client/src/translations/en_us/common.json | 7 +- client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + 11 files changed, 250 insertions(+), 16 deletions(-) create mode 100644 client/src/components/dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 042ad7c33..3858948d7 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -10689,6 +10689,27 @@ titles + + monthlyemployeeefficiency + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + monthlyjobcosting false diff --git a/client/src/components/dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx b/client/src/components/dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx new file mode 100644 index 000000000..e90220698 --- /dev/null +++ b/client/src/components/dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx @@ -0,0 +1,166 @@ +import { Card } from "antd"; +import _ from "lodash"; +import moment from "moment"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { + Bar, + CartesianGrid, + ComposedChart, + Legend, + Line, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; +import * as Utils from "../../scoreboard-targets-table/scoreboard-targets-table.util"; +import DashboardRefreshRequired from "../refresh-required.component"; + +export default function DashboardMonthlyEmployeeEfficiency({ + data, + ...cardProps +}) { + const { t } = useTranslation(); + if (!data) return null; + if (!data.monthly_employee_efficiency) + return ; + + const ticketsByDate = _.groupBy(data.monthly_employee_efficiency, (item) => + moment(item.date).format("YYYY-MM-DD") + ); + + const listOfDays = Utils.ListOfDaysInCurrentMonth(); + + const chartData = listOfDays.reduce((acc, val) => { + //Sum up the current day. + let dailyHrs; + if (!!ticketsByDate[val]) { + dailyHrs = ticketsByDate[val].reduce( + (dayAcc, dayVal) => { + return { + actual: dayAcc.actual + dayVal.actualhrs, + productive: dayAcc.actual + dayVal.productivehrs, + }; + }, + { actual: 0, productive: 0 } + ); + } else { + dailyHrs = { actual: 0, productive: 0 }; + } + + const dailyEfficiency = + ((dailyHrs.productive - dailyHrs.actual) / dailyHrs.productive + 1) * 100; + + const theValue = { + date: moment(val).format("DD"), + ...dailyHrs, + dailyEfficiency: isNaN(dailyEfficiency) ? 0 : dailyEfficiency.toFixed(1), + accActual: + acc.length > 0 + ? acc[acc.length - 1].accActual + dailyHrs.actual + : dailyHrs.actual, + + accProductive: + acc.length > 0 + ? acc[acc.length - 1].accProductive + dailyHrs.productive + : dailyHrs.productive, + accEfficiency: 0, + }; + theValue.accEfficiency = ( + ((theValue.accProductive - theValue.accActual) / + (theValue.accProductive || 1) + + 1) * + 100 + ).toFixed(1); + + return [...acc, theValue]; + }, []); + + return ( + +
+ + + + + + + + + + + + + + +
+
+ ); +} + +export const DashboardMonthlyEmployeeEfficiencyGql = ` + monthly_employee_efficiency: timetickets(where: {_and: [{date: {_gte: "${moment() + .startOf("month") + .format("YYYY-MM-DD")}"}},{date: {_lte: "${moment() + .endOf("month") + .format("YYYY-MM-DD")}"}} ]}) { + actualhrs + productivehrs + employeeid + employee { + first_name + last_name + } + date + } +`; diff --git a/client/src/components/dashboard-components/total-production-hours/total-production-hours.component.jsx b/client/src/components/dashboard-components/total-production-hours/total-production-hours.component.jsx index 81a852400..9cdf7fc68 100644 --- a/client/src/components/dashboard-components/total-production-hours/total-production-hours.component.jsx +++ b/client/src/components/dashboard-components/total-production-hours/total-production-hours.component.jsx @@ -42,11 +42,17 @@ export function DashboardTotalProductionHours({ return ( - - + + diff --git a/client/src/components/dashboard-grid/dashboard-grid.component.jsx b/client/src/components/dashboard-grid/dashboard-grid.component.jsx index b7fa83049..6b87d0d7a 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.component.jsx +++ b/client/src/components/dashboard-grid/dashboard-grid.component.jsx @@ -1,6 +1,7 @@ import Icon, { SyncOutlined } from "@ant-design/icons"; import { gql, useMutation, useQuery } from "@apollo/client"; import { Button, Dropdown, Menu, notification, PageHeader, Space } from "antd"; +import i18next from "i18next"; import _ from "lodash"; import moment from "moment"; import React, { useState } from "react"; @@ -16,6 +17,9 @@ import { selectCurrentUser, } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; +import DashboardMonthlyEmployeeEfficiency, { + DashboardMonthlyEmployeeEfficiencyGql, +} from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component"; import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component"; import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component"; import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component"; @@ -195,7 +199,7 @@ export default connect( const componentList = { ProductionDollars: { - label: "Production Dollars", + label: i18next.t("dashboard.titles.productiondollars"), component: DashboardTotalProductionDollars, gqlFragment: null, w: 1, @@ -204,7 +208,7 @@ const componentList = { minH: 1, }, ProductionHours: { - label: "Production Hours", + label: i18next.t("dashboard.titles.productionhours"), component: DashboardTotalProductionHours, gqlFragment: DashboardTotalProductionHoursGql, w: 3, @@ -213,7 +217,7 @@ const componentList = { minH: 1, }, ProjectedMonthlySales: { - label: "Projected Monthly Sales", + label: i18next.t("dashboard.titles.projectedmonthlysales"), component: DashboardProjectedMonthlySales, gqlFragment: DashboardProjectedMonthlySalesGql, w: 2, @@ -222,7 +226,7 @@ const componentList = { minH: 1, }, MonthlyRevenueGraph: { - label: "Monthly Sales Graph", + label: i18next.t("dashboard.titles.monthlyrevenuegraph"), component: DashboardMonthlyRevenueGraph, gqlFragment: DashboardMonthlyRevenueGraphGql, w: 4, @@ -231,7 +235,7 @@ const componentList = { minH: 2, }, MonthlyJobCosting: { - label: "Monthly Job Costing", + label: i18next.t("dashboard.titles.monthlyjobcosting"), component: DashboardMonthlyJobCosting, gqlFragment: null, minW: 6, @@ -240,7 +244,7 @@ const componentList = { h: 3, }, MonthlyPartsSales: { - label: "Monthly Parts Sales", + label: i18next.t("dashboard.titles.productiondollars"), component: DashboardMonthlyPartsSales, gqlFragment: null, minW: 2, @@ -249,7 +253,7 @@ const componentList = { h: 2, }, MonthlyLaborSales: { - label: "Monthly Parts Sales", + label: i18next.t("dashboard.titles.monthlypartssales"), component: DashboardMonthlyLaborSales, gqlFragment: null, minW: 2, @@ -257,6 +261,15 @@ const componentList = { w: 2, h: 2, }, + MonthlyEmployeeEfficency: { + label: i18next.t("dashboard.titles.monthlyemployeeefficiency"), + component: DashboardMonthlyEmployeeEfficiency, + gqlFragment: DashboardMonthlyEmployeeEfficiencyGql, + minW: 2, + minH: 2, + w: 2, + h: 2, + }, }; const createDashboardQuery = (state) => { diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 5069e3df6..f1004f4f4 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -317,7 +317,9 @@ function Header({ window.open("https://help.imex.online/", "_blank"); }} icon={} - /> + > + {t("menus.header.help")} + { diff --git a/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx b/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx index 95bad7368..e99a109c4 100644 --- a/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx +++ b/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx @@ -1,4 +1,4 @@ -import { Collapse, Form, Input, Select, Switch } from "antd"; +import { Collapse, Form, Input, InputNumber, Select, Switch } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -240,6 +240,26 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) { + + + + + + + + + + + diff --git a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.js b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.js index f80c0118a..e3a48640c 100644 --- a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.js +++ b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header-graph.component.js @@ -53,7 +53,7 @@ export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) { name="Ideal Load" dataKey="target" stroke="darkgreen" - fill="whitr" + fill="white" fillOpacity={0} /> Date: Wed, 16 Jun 2021 11:41:33 -0700 Subject: [PATCH 2/3] Styling fixes to allow for printing of pages. --- .../chat-affix/chat-affix.container.jsx | 40 +++++------ .../chat-affix/chat-affix.styles.scss | 7 +- .../components/header/header.component.jsx | 12 +--- .../pages/manage/manage.page.component.jsx | 66 ++++++++++--------- .../src/pages/manage/manage.page.styles.scss | 4 +- 5 files changed, 62 insertions(+), 67 deletions(-) diff --git a/client/src/components/chat-affix/chat-affix.container.jsx b/client/src/components/chat-affix/chat-affix.container.jsx index 22ba1fdd9..170ad3b55 100644 --- a/client/src/components/chat-affix/chat-affix.container.jsx +++ b/client/src/components/chat-affix/chat-affix.container.jsx @@ -1,17 +1,15 @@ import { useSubscription } from "@apollo/client"; import React from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; import { CONVERSATION_LIST_SUBSCRIPTION } from "../../graphql/conversations.queries"; +import { selectChatVisible } from "../../redux/messaging/messaging.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import ChatAffixComponent from "./chat-affix.component"; -import { Affix } from "antd"; import "./chat-affix.styles.scss"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { selectBodyshop } from "../../redux/user/user.selectors"; -import { selectChatVisible } from "../../redux/messaging/messaging.selectors"; - const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, chatVisible: selectChatVisible, @@ -31,22 +29,20 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) { if (!bodyshop || !bodyshop.messagingservicesid) return <>; return ( - -
- {bodyshop && bodyshop.messagingservicesid ? ( - { - return (acc = acc + val.messages_aggregate.aggregate.count); - }, 0)) || - 0 - } - /> - ) : null} -
-
+
+ {bodyshop && bodyshop.messagingservicesid ? ( + { + return (acc = acc + val.messages_aggregate.aggregate.count); + }, 0)) || + 0 + } + /> + ) : null} +
); } export default connect(mapStateToProps, null)(ChatAffixContainer); diff --git a/client/src/components/chat-affix/chat-affix.styles.scss b/client/src/components/chat-affix/chat-affix.styles.scss index 807a3ff93..7d8fecff3 100644 --- a/client/src/components/chat-affix/chat-affix.styles.scss +++ b/client/src/components/chat-affix/chat-affix.styles.scss @@ -1,6 +1,11 @@ .chat-affix { - position: absolute; + position: fixed; + left: 2vw; bottom: 2vh; + z-index: 999; + -webkit-box-shadow: 0px 0px 2px 0px rgba(69, 69, 69, 1); + -moz-box-shadow: 0px 0px 2px 0px rgba(69, 69, 69, 1); + box-shadow: 0px 0px 2px 0px rgba(69, 69, 69, 1); } .chat-affix-open { diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index f1004f4f4..e0a44da56 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -1,5 +1,4 @@ import Icon, { - FileAddOutlined, BankFilled, BarChartOutlined, CarFilled, @@ -9,6 +8,7 @@ import Icon, { ExportOutlined, FieldTimeOutlined, FileAddFilled, + FileAddOutlined, FileFilled, GlobalOutlined, HomeFilled, @@ -85,7 +85,6 @@ function Header({ mode="horizontal" //theme="light" theme={"dark"} - style={{ flex: "auto" }} selectedKeys={[selectedHeader]} onClick={handleMenuClick} subMenuCloseDelay={0.3} @@ -263,7 +262,6 @@ function Header({ {t("menus.header.temporarydocs")}
- - - } - > + }> {recentItems.map((i, idx) => ( {i.label} diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index a1400870a..dbf37cca1 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -378,41 +378,43 @@ export function Manage({ match, conflict, bodyshop }) { else PageContent = AppRouteTable; return ( - - + <> + + + - - - - {PageContent} - - -
-
-
-
- {`ImEX Online ${ - process.env.REACT_APP_GIT_SHA - } - ${preval`module.exports = new Date().toLocaleString("en-US", {timeZone: "America/Los_Angeles"});`}`} + + + + {PageContent} + +
+
+
+
+ {`ImEX Online ${ + process.env.REACT_APP_GIT_SHA + } - ${preval`module.exports = new Date().toLocaleString("en-US", {timeZone: "America/Los_Angeles"});`}`} +
+
-
+ + Disclaimer & Notices + +
- - Disclaimer - - -
-
-
- +
+
+
+ ); } export default connect(mapStateToProps, null)(Manage); diff --git a/client/src/pages/manage/manage.page.styles.scss b/client/src/pages/manage/manage.page.styles.scss index 845ead709..9084e96bb 100644 --- a/client/src/pages/manage/manage.page.styles.scss +++ b/client/src/pages/manage/manage.page.styles.scss @@ -3,6 +3,6 @@ } .layout-container { - height: 100vh; - overflow-y: auto; + // height: 100vh; + // overflow-y: auto; } From ba8c8bc976b4c5a3644efcc4c93f4b00485095e7 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 16 Jun 2021 12:07:56 -0700 Subject: [PATCH 3/3] IO-1210 Jobs Close Updates --- bodyshop_translations.babel | 84 +++++++++++ client/src/graphql/jobs.queries.js | 6 + .../pages/jobs-close/jobs-close.component.jsx | 133 +++++++++++++++--- client/src/translations/en_us/common.json | 4 + client/src/translations/es/common.json | 4 + client/src/translations/fr/common.json | 4 + 6 files changed, 214 insertions(+), 21 deletions(-) diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 3858948d7..9f9abea4e 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -20793,6 +20793,69 @@ labels + + actual_completion_inferred + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + actual_delivery_inferred + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + actual_in_inferred + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + additionaltotal false @@ -21454,6 +21517,27 @@ + + closejob + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + contracts false diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 62ba766cb..8bb3f5faa 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -1696,6 +1696,12 @@ export const QUERY_JOB_CLOSE_DETAILS = gql` date_exported date_invoiced voided + scheduled_completion + actual_completion + scheduled_delivery + actual_delivery + scheduled_in + actual_in joblines(where: { removed: { _eq: false } }) { id removed diff --git a/client/src/pages/jobs-close/jobs-close.component.jsx b/client/src/pages/jobs-close/jobs-close.component.jsx index 8ca5aa331..e4a47f869 100644 --- a/client/src/pages/jobs-close/jobs-close.component.jsx +++ b/client/src/pages/jobs-close/jobs-close.component.jsx @@ -1,5 +1,14 @@ import { useApolloClient, useMutation } from "@apollo/client"; -import { Button, Form, notification, Popconfirm, Space } from "antd"; +import { + Button, + Form, + notification, + Popconfirm, + Space, + Alert, + Divider, + PageHeader, +} from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -13,6 +22,9 @@ import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.qu import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component"; +import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component"; +import moment from "moment"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, jobRO: selectJobReadOnly, @@ -82,31 +94,110 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) { layout="vertical" form={form} onFinish={handleFinish} - initialValues={{ joblines: job.joblines }} + initialValues={{ + joblines: job.joblines, + actual_in: job.actual_in + ? moment(job.actual_in) + : job.scheduled_in && moment(job.scheduled_in), + actual_completion: job.actual_completion + ? moment(job.actual_completion) + : job.scheduled_completion && moment(job.scheduled_completion), + actual_delivery: job.actual_delivery + ? moment(job.actual_delivery) + : job.scheduled_delivery && moment(job.scheduled_delivery), + }} scrollToFirstError > - - + + - form.submit()} - disabled={jobRO} - okText={t("general.labels.yes")} - cancelText={t("general.labels.no")} - title={t("jobs.labels.closeconfirm")} - > - - + form.submit()} + disabled={jobRO} + okText={t("general.labels.yes")} + cancelText={t("general.labels.no")} + title={t("jobs.labels.closeconfirm")} + > + + - + + + } + /> + + + + {!job.actual_in && job.scheduled_in && ( + + )} + {!job.actual_completion && job.scheduled_completion && ( + + )} + {!job.actual_delivery && job.scheduled_delivery && ( + + )} - + + + + + + {() => { + return ( + + + + ); + }} + + + + + + diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index e18d5c4de..b96c433a6 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1250,6 +1250,9 @@ "scheddates": "Schedule Dates" }, "labels": { + "actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).", + "actual_delivery_inferred": "$t(jobs.fields.actual_delivery) inferred using $t(jobs.fields.scheduled_delivery).", + "actual_in_inferred": "$t(jobs.fields.actual_in) inferred using $t(jobs.fields.scheduled_in).", "additionaltotal": "Additional Total", "adjustmentrate": "Adjustment Rate", "adjustments": "Adjustments", @@ -1285,6 +1288,7 @@ "checklistdocuments": "Checklist Documents", "checklists": "Checklists", "closeconfirm": "Are you sure you want to close this job? This cannot be easily undone.", + "closejob": "Close Job {{ro_number}}", "contracts": "CC Contracts", "cost": "Cost", "cost_labor": "Cost - Labor", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index f5e275ccb..eae5783f5 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1250,6 +1250,9 @@ "scheddates": "" }, "labels": { + "actual_completion_inferred": "", + "actual_delivery_inferred": "", + "actual_in_inferred": "", "additionaltotal": "", "adjustmentrate": "", "adjustments": "", @@ -1285,6 +1288,7 @@ "checklistdocuments": "", "checklists": "", "closeconfirm": "", + "closejob": "", "contracts": "", "cost": "", "cost_labor": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index a0806f97c..8f4784000 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1250,6 +1250,9 @@ "scheddates": "" }, "labels": { + "actual_completion_inferred": "", + "actual_delivery_inferred": "", + "actual_in_inferred": "", "additionaltotal": "", "adjustmentrate": "", "adjustments": "", @@ -1285,6 +1288,7 @@ "checklistdocuments": "", "checklists": "", "closeconfirm": "", + "closejob": "", "contracts": "", "cost": "", "cost_labor": "",