diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 042ad7c33..9f9abea4e 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 @@ -20772,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 @@ -21433,6 +21517,27 @@ + + closejob + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + contracts false 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/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..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")} - } - /> + > + {t("menus.header.help")} + { @@ -352,12 +351,7 @@ function Header({ - - } - > + }> {recentItems.map((i, idx) => ( {i.label} 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} /> - - + + - 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/pages/jobs-create/jobs-create.container.jsx b/client/src/pages/jobs-create/jobs-create.container.jsx index 761b1af85..079a109c9 100644 --- a/client/src/pages/jobs-create/jobs-create.container.jsx +++ b/client/src/pages/jobs-create/jobs-create.container.jsx @@ -162,6 +162,9 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { tax_sub_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, tax_lbr_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, tax_levies_rt: bodyshop.bill_tax_rates.state_tax_rate / 100, + federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100, + state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100, + local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100, parts_tax_rates: { PAA: { prt_type: "PAA", 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} - - - + + + ); } 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; } diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 662530f70..b96c433a6 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -674,18 +674,19 @@ }, "labels": { "bodyhrs": "Body Hrs", - "dollarsinproduction": "Dollars in Production'", + "dollarsinproduction": "Dollars in Production", "prodhrs": "Production Hrs", "refhrs": "Refinish Hrs" }, "titles": { + "monthlyemployeeefficiency": "Monthly Employee Efficiency", "monthlyjobcosting": "Monthly Job Costing ", "monthlylaborsales": "Monthly Labor Sales", "monthlypartssales": "Monthly Parts Sales", "monthlyrevenuegraph": "Monthly Revenue Graph", "prodhrssummary": "Production Hours Summary", - "productiondollars": "Total dollars in production", - "productionhours": "Total hours in production", + "productiondollars": "Total dollars in Production", + "productionhours": "Total hours in Production", "projectedmonthlysales": "Projected Monthly Sales" } }, @@ -1249,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", @@ -1284,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 0c6b45e48..eae5783f5 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -679,6 +679,7 @@ "refhrs": "" }, "titles": { + "monthlyemployeeefficiency": "", "monthlyjobcosting": "", "monthlylaborsales": "", "monthlypartssales": "", @@ -1249,6 +1250,9 @@ "scheddates": "" }, "labels": { + "actual_completion_inferred": "", + "actual_delivery_inferred": "", + "actual_in_inferred": "", "additionaltotal": "", "adjustmentrate": "", "adjustments": "", @@ -1284,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 6bce4fe7e..8f4784000 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -679,6 +679,7 @@ "refhrs": "" }, "titles": { + "monthlyemployeeefficiency": "", "monthlyjobcosting": "", "monthlylaborsales": "", "monthlypartssales": "", @@ -1249,6 +1250,9 @@ "scheddates": "" }, "labels": { + "actual_completion_inferred": "", + "actual_delivery_inferred": "", + "actual_in_inferred": "", "additionaltotal": "", "adjustmentrate": "", "adjustments": "", @@ -1284,6 +1288,7 @@ "checklistdocuments": "", "checklists": "", "closeconfirm": "", + "closejob": "", "contracts": "", "cost": "", "cost_labor": "",