diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx index 65140d6b0..35b4a785c 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx @@ -3,7 +3,6 @@ import axios from "axios"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { logImEXEvent } from "../../firebase/firebase.utils"; -import cleanAxios from "../../utils/CleanAxios"; import formatBytes from "../../utils/formatbytes"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -12,7 +11,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = () => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); @@ -26,7 +25,7 @@ const mapDispatchToProps = (dispatch) => ({ export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyDownloadButton); -export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, identifier, jobId }) { +export function JobsDocumentsImgproxyDownloadButton({ galleryImages, identifier, jobId }) { const { t } = useTranslation(); const [download, setDownload] = useState(null); const [loading, setLoading] = useState(false); diff --git a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx index b00ff0c36..faea7a23d 100644 --- a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx +++ b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx @@ -1,9 +1,9 @@ import Icon from "@ant-design/icons"; import { Card, Popover, Space } from "antd"; -import _ from "lodash"; +import { groupBy } from "lodash"; import dayjs from "../../utils/day"; -import React, { useMemo } from "react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { MdFileDownload, MdFileUpload } from "react-icons/md"; import { connect } from "react-redux"; @@ -26,21 +26,12 @@ const mapStateToProps = createStructuredSelector({ calculating: selectScheduleLoadCalculating }); -const mapDispatchToProps = (dispatch) => ({}); +const mapDispatchToProps = () => ({}); -export function ScheduleCalendarHeaderComponent({ - bodyshop, - label, - refetch, - date, - load, - calculating, - events, - ...otherProps -}) { +export function ScheduleCalendarHeaderComponent({ bodyshop, label, refetch, date, load, calculating, events }) { const ATSToday = useMemo(() => { if (!events) return []; - return _.groupBy( + return groupBy( events.filter((e) => !e.vacation && e.isintake && dayjs(date).isSame(dayjs(e.start), "day")), "job.alt_transport" ); @@ -155,7 +146,11 @@ export function ScheduleCalendarHeaderComponent({ - {`${(loadData.allHoursInBody || 0) && loadData.allHoursInBody.toFixed(1)}/${(loadData.allHoursInRefinish || 0) && loadData.allHoursInRefinish.toFixed(1)}/${(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(1)}`} + + {`${(loadData.allHoursInBody || 0) && loadData.allHoursInBody.toFixed(1)}/${ + (loadData.allHoursInRefinish || 0) && loadData.allHoursInRefinish.toFixed(1) + }/${(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(1)}`} + diff --git a/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx b/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx index 7b613aaed..2ab5ba7a3 100644 --- a/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx +++ b/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx @@ -1,8 +1,7 @@ import { Card } from "antd"; import Dinero from "dinero.js"; -import _ from "lodash"; +import { round } from "lodash"; import dayjs from "../../utils/day"; -import React from "react"; import { connect } from "react-redux"; import { Area, @@ -29,7 +28,7 @@ const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = () => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardChart); @@ -40,7 +39,7 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) { const data = listOfBusDays.reduce((acc, val) => { //Sum up the current day. let dayhrs; - if (!!sbEntriesByDate[val]) { + if (sbEntriesByDate[val]) { dayhrs = sbEntriesByDate[val].reduce( (dayAcc, dayVal) => { return { @@ -61,9 +60,9 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) { const theValue = { date: dayjs(val).format("D ddd"), - paintHrs: _.round(dayhrs.painthrs, 1), - bodyHrs: _.round(dayhrs.bodyhrs, 1), - accTargetHrs: _.round( + paintHrs: round(dayhrs.painthrs, 1), + bodyHrs: round(dayhrs.bodyhrs, 1), + accTargetHrs: round( Utils.AsOfDateTargetHours( bodyshop.scoreboard_target.dailyBodyTarget + bodyshop.scoreboard_target.dailyPaintTarget, val @@ -72,14 +71,14 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) { bodyshop.scoreboard_target.dailyPaintTarget, 1 ), - accHrs: _.round( + accHrs: round( acc.length > 0 ? acc[acc.length - 1].accHrs + dayhrs.painthrs + dayhrs.bodyhrs : dayhrs.painthrs + dayhrs.bodyhrs, 1 ), - sales: _.round(dayhrs.sales, 2), - accSales: _.round(acc.length > 0 ? acc[acc.length - 1].accSales + dayhrs.sales : dayhrs.sales, 2) + sales: round(dayhrs.sales, 2), + accSales: round(acc.length > 0 ? acc[acc.length - 1].accSales + dayhrs.sales : dayhrs.sales, 2) }; return [...acc, theValue]; diff --git a/client/src/components/scoreboard-display/scoreboard-display.component.jsx b/client/src/components/scoreboard-display/scoreboard-display.component.jsx index 3d8a2f98a..053aef778 100644 --- a/client/src/components/scoreboard-display/scoreboard-display.component.jsx +++ b/client/src/components/scoreboard-display/scoreboard-display.component.jsx @@ -1,23 +1,25 @@ -import { Col, Row } from "antd"; -import { useEffect } from "react"; +import { Col, Row, Spin } from "antd"; +import { useEffect, useState } from "react"; import ScoreboardChart from "../scoreboard-chart/scoreboard-chart.component"; import ScoreboardLastDays from "../scoreboard-last-days/scoreboard-last-days.component"; import ScoreboardTargetsTable from "../scoreboard-targets-table/scoreboard-targets-table.component"; - import { useApolloClient, useQuery } from "@apollo/client"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { GET_BLOCKED_DAYS, QUERY_SCOREBOARD } from "../../graphql/scoreboard.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import dayjs from "../../utils/day"; +import { + clearHolidays, + clearWorkingWeekdays, + setHolidays, + setWorkingWeekdays +} from "../scoreboard-targets-table/scoreboard-targets-table.util"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); +const mapDispatchToProps = () => ({}); export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardDisplayComponent); export function ScoreboardDisplayComponent({ bodyshop }) { @@ -32,7 +34,6 @@ export function ScoreboardDisplayComponent({ bodyshop }) { const { data } = scoreboardSubscription; const client = useApolloClient(); const scoreBoardlist = data?.scoreboard || []; - const sbEntriesByDate = {}; scoreBoardlist.forEach((i) => { @@ -43,35 +44,52 @@ export function ScoreboardDisplayComponent({ bodyshop }) { sbEntriesByDate[entryDate].push(i); }); - useEffect(() => { - //Update the locals. - async function setDayJSSettings() { - let appointments; + const [loading, setLoading] = useState(true); // Loading state - if (!bodyshop.scoreboard_target.ignoreblockeddays) { - const { data } = await client.query({ - query: GET_BLOCKED_DAYS, - variables: { - start: dayjs().startOf("month"), - end: dayjs().endOf("month") - } - }); - appointments = data.appointments; - } - dayjs.updateLocale(dayjs.locale(), { - workingWeekdays: translateSettingsToWorkingDays(bodyshop.workingdays), - ...(appointments?.length - ? { - holidays: appointments.map((h) => dayjs(h.start).format("MM-DD-YYYY")) + useEffect(() => { + async function setDayJSSettings() { + try { + let appointments; + + if (!bodyshop.scoreboard_target.ignoreblockeddays) { + const { data } = await client.query({ + query: GET_BLOCKED_DAYS, + variables: { + start: dayjs().startOf("month"), + end: dayjs().endOf("month") } - : {}), - holidayFormat: "MM-DD-YYYY" - }); + }); + appointments = data.appointments; + } + + const holidays = appointments ? appointments.map((h) => dayjs(h.start).format("MM-DD-YYYY")) : []; + const workingWeekdays = translateSettingsToWorkingDays(bodyshop.workingdays); + + // Set holidays and working weekdays + setHolidays(holidays); + setWorkingWeekdays(workingWeekdays); + } finally { + setLoading(false); // Set loading to false after processing + } } setDayJSSettings(); + + // Cleanup on unmount + return () => { + clearHolidays(); + clearWorkingWeekdays(); + }; }, [client, bodyshop]); + if (loading) { + return ( + + + + ); + } + return ( @@ -89,27 +107,12 @@ export function ScoreboardDisplayComponent({ bodyshop }) { function translateSettingsToWorkingDays(workingdays) { const days = []; - - if (workingdays.monday) { - days.push(1); - } - if (workingdays.tuesday) { - days.push(2); - } - if (workingdays.wednesday) { - days.push(3); - } - if (workingdays.thursday) { - days.push(4); - } - if (workingdays.friday) { - days.push(5); - } - if (workingdays.saturday) { - days.push(6); - } - if (workingdays.sunday) { - days.push(0); - } + if (workingdays.monday) days.push(1); + if (workingdays.tuesday) days.push(2); + if (workingdays.wednesday) days.push(3); + if (workingdays.thursday) days.push(4); + if (workingdays.friday) days.push(5); + if (workingdays.saturday) days.push(6); + if (workingdays.sunday) days.push(0); return days; } diff --git a/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx b/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx index d511bc96e..f0140d8e7 100644 --- a/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx +++ b/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx @@ -1,4 +1,3 @@ -import React from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; @@ -10,7 +9,7 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = () => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); @@ -26,7 +25,7 @@ export function ScoreboardLastDays({ bodyshop, sbEntriesByDate }) { {ArrayOfDate.map((a) => ( - {!!sbEntriesByDate ? : } + {sbEntriesByDate ? : } ))} diff --git a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx index c017b0267..ebc3b52b9 100644 --- a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx +++ b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx @@ -1,8 +1,8 @@ import { CalendarOutlined } from "@ant-design/icons"; import { Card, Col, Divider, Row, Statistic } from "antd"; -import _ from "lodash"; +import { groupBy } from "lodash"; import dayjs from "../../utils/day"; -import React, { useMemo } from "react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -13,7 +13,7 @@ import * as Util from "./scoreboard-targets-table.util"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = () => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); @@ -24,7 +24,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) { const { t } = useTranslation(); const values = useMemo(() => { - const dateHash = _.groupBy(scoreBoardlist, "date"); + const dateHash = groupBy(scoreBoardlist, "date"); let ret = { todayBody: 0, @@ -213,4 +213,5 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) { ); } + export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardTargetsTable); diff --git a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js index 32703881c..af6ace810 100644 --- a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js +++ b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js @@ -1,29 +1,172 @@ import dayjs from "../../utils/day"; -export const CalculateWorkingDaysThisMonth = () => dayjs().endOf("month").businessDaysInMonth().length; +const DEFAULT_WORKING_DAYS = [1, 2, 3, 4, 5]; // Default to Monday-Friday -export const CalculateWorkingDaysInPeriod = (start, end) => dayjs(end).businessDiff(dayjs(start)); +// Module-level state for holidays and working weekdays +let holidays = []; +let workingWeekdays = DEFAULT_WORKING_DAYS; -export const CalculateWorkingDaysAsOfToday = () => dayjs().endOf("day").businessDiff(dayjs().startOf("month")); +/** + * Sets the holidays for the business logic. + * @param newHolidays + */ +export const setHolidays = (newHolidays = []) => { + holidays = newHolidays; +}; -export const CalculateWorkingDaysLastMonth = () => - dayjs().subtract(1, "month").endOf("month").businessDaysInMonth().length; +/** + * Clears the holidays. + */ +export const clearHolidays = () => { + holidays = []; +}; +/** + * Sets the working weekdays for the business logic. + * @param newWorkingWeekdays + */ +export const setWorkingWeekdays = (newWorkingWeekdays = DEFAULT_WORKING_DAYS) => { + workingWeekdays = newWorkingWeekdays; +}; + +/** + * Clears the working weekdays, resetting to default (Monday-Friday). + */ +export const clearWorkingWeekdays = () => { + workingWeekdays = DEFAULT_WORKING_DAYS; // Reset to default +}; + +/** + * Translates the bodyshop working days settings to an array of weekdays. + * @returns {*[]} + */ +export const getHolidays = () => { + return holidays; +}; + +/** + * Translates the working days settings from the bodyshop to an array of weekdays. + * @returns {number[]} + */ +export const getWorkingWeekdays = () => { + return workingWeekdays; +}; + +/** + * Calculates the number of working days in the current month, excluding holidays. + * @returns {number} + * @constructor + */ +export const CalculateWorkingDaysThisMonth = () => { + const businessDays = dayjs().businessDaysInMonth(); + return businessDays.filter((day) => !holidays.includes(dayjs(day).format("MM-DD-YYYY"))).length; +}; + +/** + * Calculates the number of working days in a given period, excluding holidays. + * @param start + * @param end + * @returns {number} + * @constructor + */ +export const CalculateWorkingDaysInPeriod = (start, end) => { + let businessDays = dayjs(end).businessDiff(dayjs(start)); + if (dayjs(end).isBusinessDay() && !holidays.includes(dayjs(end).format("MM-DD-YYYY"))) { + businessDays += 1; + } + return businessDays; +}; + +/** + * Calculates the number of working days as of today, excluding holidays. + * @returns {number} + * @constructor + */ +export const CalculateWorkingDaysAsOfToday = () => { + const today = dayjs().startOf("day"); + let businessDays = today.businessDiff(dayjs().startOf("month")); + if (today.isBusinessDay() && !holidays.includes(today.format("MM-DD-YYYY"))) { + businessDays += 1; + } + return businessDays; +}; + +/** + * Calculates the number of working days in the last month, excluding holidays. + * @returns {number} + * @constructor + */ +export const CalculateWorkingDaysLastMonth = () => { + const businessDays = dayjs().subtract(1, "month").businessDaysInMonth(); + return businessDays.filter((day) => !holidays.includes(dayjs(day).format("MM-DD-YYYY"))).length; +}; + +/** + * Calculates the weekly target hours based on daily target hours and the number of working days in the current week. + * @param dailyTargetHrs + * @returns {number} + * @constructor + */ export const WeeklyTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysInPeriod(dayjs().startOf("week"), dayjs().endOf("week")); +/** + * Calculates the weekly target hours for a specific period. + * @param dailyTargetHrs + * @param start + * @param end + * @returns {number} + * @constructor + */ export const WeeklyTargetHrsInPeriod = (dailyTargetHrs, start, end) => dailyTargetHrs * CalculateWorkingDaysInPeriod(start, end); +/** + * Calculates the monthly target hours based on daily target hours and the number of working days in the current month. + * @param dailyTargetHrs + * @returns {number} + * @constructor + */ export const MonthlyTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysThisMonth(); +/** + * Calculates the monthly target hours for the last month based on daily target hours and the number of working days + * in the last month. + * @param dailyTargetHrs + * @returns {number} + * @constructor + */ export const LastMonthTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysLastMonth(); +/** + * Calculates the target hours as of today based on daily target hours and the number of working days as of today. + * @param dailyTargetHrs + * @returns {number} + * @constructor + */ export const AsOfTodayTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysAsOfToday(); -export const AsOfDateTargetHours = (dailyTargetHours, date) => - dailyTargetHours * dayjs(date).businessDiff(dayjs().startOf("month")); +/** + * Calculates the target hours as of a specific date based on daily target hours and the number of business days up to + * that date. + * @param dailyTargetHours + * @param date + * @returns {number} + * @constructor + */ +export const AsOfDateTargetHours = (dailyTargetHours, date) => { + let businessDays = dayjs(date).businessDiff(dayjs().startOf("month")); + if (dayjs(date).isBusinessDay() && !holidays.includes(dayjs(date).format("MM-DD-YYYY"))) { + businessDays += 1; + } + return dailyTargetHours * businessDays; +}; +/** + * Generates a list of all days in the current month. + * @returns {*[]} + * @constructor + */ export const ListOfDaysInCurrentMonth = () => { const days = []; let dateStart = dayjs().startOf("month"); @@ -36,6 +179,13 @@ export const ListOfDaysInCurrentMonth = () => { return days; }; +/** + * Generates a list of all days between two dates. + * @param start + * @param end + * @returns {*[]} + * @constructor + */ export const ListDaysBetween = ({ start, end }) => { const days = []; let dateStart = dayjs(start);