From bb3d3fbe723103c2de2eaa56fccb6ffb272f6151 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 22 Aug 2024 11:35:15 -0700 Subject: [PATCH 01/24] IO-2520 Change Logging back to default and adjust start and end to be default Datapump will be run daily as per Sofia Signed-off-by: Allan Carr --- server/data/kaizen.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/server/data/kaizen.js b/server/data/kaizen.js index 1dccef61c..5a09bec24 100644 --- a/server/data/kaizen.js +++ b/server/data/kaizen.js @@ -56,8 +56,8 @@ exports.default = async (req, res) => { try { const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, { bodyshopid: bodyshop.id, - start: start ? moment(start).startOf("hours") : moment().subtract(2, "hours").startOf("hour"), - ...(end && { end: moment(end).endOf("hours") }) + start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"), + ...(end && { end: moment(end).endOf("day") }) }); const kaizenObject = { @@ -176,24 +176,19 @@ exports.default = async (req, res) => { } finally { sftp.end(); } - // sendServerEmail({ - // subject: `Kaizen Report ${moment().format("MM-DD-YY")}`, - // text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} - // Uploaded: ${JSON.stringify( - // allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })), - // null, - // 2 - // )} - // `, - // }); + sendServerEmail({ + subject: `Kaizen Report ${moment().format("MM-DD-YY")}`, + text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} + Uploaded: ${JSON.stringify( + allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })), + null, + 2 + )} + ` + }); res.sendStatus(200); } catch (error) { res.status(200).json(error); - sendServerEmail({ - subject: `Kaizen Report ${moment().format("MM-DD-YY @ HH:mm:ss")}`, - text: `Errors: JSON.stringify(error)} - All Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}` - }); } }; From f82478a362e58bcc0a6694801bcdfeeed13a8646 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 22 Aug 2024 13:11:00 -0700 Subject: [PATCH 02/24] IO-2895 Adjustment to Bottom Line Signed-off-by: Allan Carr --- server/job/job-totals-USA.js | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index 3dabed316..ca9a74bca 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -965,22 +965,17 @@ function CalculateTaxesTotals(job, otherTotals) { } }); - if (job.adjustment_bottom_line) { - const subtotal_before_adjustment = subtotal.add(Dinero({ amount: Math.round(job.adjustment_bottom_line * -100) })); - const percent_of_adjustment = - Math.round( - subtotal_before_adjustment.toUnit() / - (job.adjustment_bottom_line > 0 ? job.adjustment_bottom_line : job.adjustment_bottom_line * -1) - ) / 100; - - Object.keys(taxableAmountsByTier).forEach((taxTierKey) => { - taxable_adjustment = taxableAmountsByTier[taxTierKey].multiply(percent_of_adjustment); - if (job.adjustment_bottom_line > 0) { - taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].add(taxable_adjustment); - } else { - taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].subtract(taxable_adjustment); + if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) { + for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { + if (IsTrueOrYes(pfp["PAN"][`prt_tx_in${tyCounter}`])) { + //This amount is taxable for this type. + taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add( + Dinero({ + amount: Math.round(job.adjustment_bottom_line * 100) + }) + ); } - }); + } } const remainingTaxableAmounts = taxableAmountsByTier; From 35ac0b0c6a13fb0aa3fadf7c97b35bb421a27b06 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 22 Aug 2024 16:36:00 -0700 Subject: [PATCH 03/24] IO-2890 Kaizen Datapump Cron Signed-off-by: Allan Carr --- hasura/metadata/cron_triggers.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hasura/metadata/cron_triggers.yaml b/hasura/metadata/cron_triggers.yaml index 31b24a000..1d051c2ae 100644 --- a/hasura/metadata/cron_triggers.yaml +++ b/hasura/metadata/cron_triggers.yaml @@ -1,3 +1,11 @@ +- name: Kaizen Data Pump + webhook: '{{HASURA_API_URL}}/data/kaizen' + schedule: 30 6 * * * + include_in_metadata: true + payload: {} + headers: + - name: x-imex-auth + value_from_env: DATAPUMP_AUTH - name: Task Reminders webhook: '{{HASURA_API_URL}}/tasks-remind-handler' schedule: '*/15 * * * *' From 89d5b1cfe46aaf13a000dc4eea3761386f9e173f Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 22 Aug 2024 16:42:42 -0700 Subject: [PATCH 04/24] IO-2892 Autohouse & Claimscorp Data Pump Cron Signed-off-by: Allan Carr --- hasura/metadata/cron_triggers.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/hasura/metadata/cron_triggers.yaml b/hasura/metadata/cron_triggers.yaml index 31b24a000..c9aa560ec 100644 --- a/hasura/metadata/cron_triggers.yaml +++ b/hasura/metadata/cron_triggers.yaml @@ -1,3 +1,27 @@ +- name: AutoHouse Data Pump + webhook: '{{HASURA_API_URL}}/data/ah' + schedule: 0 7 * * * + include_in_metadata: true + payload: {} + headers: + - name: x-imex-auth + value_from_env: DATAPUMP_AUTH +- name: Claimscorp Data Pump + webhook: '{{HASURA_API_URL}}/data/cc' + schedule: 30 7 * * * + include_in_metadata: true + payload: {} + headers: + - name: x-imex-auth + value: DATAPUMP_AUTH +- name: Kaizen Data Pump + webhook: '{{HASURA_API_URL}}/data/kaizen' + schedule: 30 6 * * * + include_in_metadata: true + payload: {} + headers: + - name: x-imex-auth + value_from_env: DATAPUMP_AUTH - name: Task Reminders webhook: '{{HASURA_API_URL}}/tasks-remind-handler' schedule: '*/15 * * * *' From 882038a794887f4935b1db66605c41520649d1d8 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 23 Aug 2024 15:55:54 -0400 Subject: [PATCH 05/24] - Final DateTimePicker update Signed-off-by: Dave Richer --- .../form-date-time-picker.component.jsx | 67 ++------ .../form-date-time-picker/formats.js | 151 +++++++----------- 2 files changed, 70 insertions(+), 148 deletions(-) diff --git a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx index e8f52ee31..e34ae14f7 100644 --- a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx +++ b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import React, { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; import dayjs from "../../utils/day"; -import { dateFormats, dateTimeFormats } from "./formats.js"; +import { fuzzyMatchDate } from "./formats.js"; const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => { const [isManualInput, setIsManualInput] = useState(false); @@ -11,79 +11,34 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, is const handleChange = useCallback( (newDate) => { - if (newDate === null && onChange) { - onChange(null); - } else if (newDate && onChange) { - onChange(newDate); + if (onChange) { + onChange(newDate || null); } setIsManualInput(false); }, [onChange] ); - const normalizeDateTimeString = (input) => { - const upperV = input.toUpperCase().replaceAll(".", "/").replaceAll("-", "/"); - - const [datePart, ...timeParts] = upperV.split(" "); - - if (timeParts.length === 0) { - return datePart; // If there's no time part, just return the date part. - } - - const timePart = timeParts.join(" "); // In case there are multiple spaces, join them back - - // Normalize the time part by ensuring there's a space before AM/PM if not already present - const normalizedTime = timePart.replace(/(\d{1,2})(:\d{2})?\s?(AM|PM)/, "$1$2 $3"); - - // Combine the date part with the normalized time part - return `${datePart} ${normalizedTime}`.trim(); - }; - const handleBlur = useCallback( (e) => { + // Bail if this is not a manual input if (!isManualInput) { return; } - + // Reset manual input flag setIsManualInput(false); - const v = e.target.value; + const v = e?.target?.value; + if (!v) return; - const upperV = normalizeDateTimeString(v); - let parsedDate; + let parsedDate = isDateOnly ? fuzzyMatchDate(v)?.startOf("day") : fuzzyMatchDate(v); - for (const format of isDateOnly ? dateFormats : dateTimeFormats) { - parsedDate = dayjs(upperV, format); - if (parsedDate.isValid()) break; - } - - if (parsedDate && parsedDate.isValid()) { - if (isDateOnly) { - parsedDate = parsedDate.startOf("day"); - } - - if (value && value.isValid && value.isValid()) { - parsedDate = parsedDate.set({ - hours: value.hours(), - minutes: value.minutes(), - seconds: value.seconds(), - milliseconds: value.milliseconds() - }); - } - - if (onlyFuture) { - if (dayjs().subtract(1, "day").isBefore(parsedDate)) { - onChange(parsedDate); - } else { - onChange(dayjs().startOf("day")); - } - } else { - onChange(parsedDate); - } + if (parsedDate && onChange) { + onChange(parsedDate); } }, - [isManualInput, isDateOnly, onlyFuture, onChange, value] + [isManualInput, isDateOnly, onChange] ); const handleKeyDown = useCallback( diff --git a/client/src/components/form-date-time-picker/formats.js b/client/src/components/form-date-time-picker/formats.js index b68704a72..ffe9f68c2 100644 --- a/client/src/components/form-date-time-picker/formats.js +++ b/client/src/components/form-date-time-picker/formats.js @@ -1,96 +1,63 @@ -export const dateTimeFormats = [ - // Four-digit year with time - "M/D/YYYY h:mm A", // Example: 1/5/2023 9:00 AM - "M/D/YYYY h:mmA", // Example: 1/5/2023 9:00AM - "M/D/YYYY h A", // Example: 1/5/2023 9 AM - "M/D/YYYY hA", // Example: 1/5/2023 9AM - "M/D/YYYY hh:mm A", // Example: 1/5/2023 02:25 PM - "M/D/YYYY hh:mm:ss A", // Example: 1/5/2023 02:25:45 PM +import dayjs from "../../utils/day"; - "MM/D/YYYY h:mm A", // Example: 12/5/2023 9:00 AM - "MM/D/YYYY h:mmA", // Example: 12/5/2023 9:00AM - "MM/D/YYYY h A", // Example: 12/5/2023 9 AM - "MM/D/YYYY hA", // Example: 12/5/2023 9AM - "MM/D/YYYY hh:mm A", // Example: 12/5/2023 02:25 PM - "MM/D/YYYY hh:mm:ss A", // Example: 12/5/2023 02:25:45 PM - - "M/DD/YYYY h:mm A", // Example: 1/25/2023 9:00 AM - "M/DD/YYYY h:mmA", // Example: 1/25/2023 9:00AM - "M/DD/YYYY h A", // Example: 1/25/2023 9 AM - "M/DD/YYYY hA", // Example: 1/25/2023 9AM - "M/DD/YYYY hh:mm A", // Example: 1/25/2023 02:25 PM - "M/DD/YYYY hh:mm:ss A", // Example: 1/25/2023 02:25:45 PM - - "MM/DD/YYYY h:mm A", // Example: 12/25/2023 9:00 AM - "MM/DD/YYYY h:mmA", // Example: 12/25/2023 9:00AM - "MM/DD/YYYY h A", // Example: 12/25/2023 9 AM - "MM/DD/YYYY hA", // Example: 12/25/2023 9AM - "MM/DD/YYYY hh:mm A", // Example: 12/25/2023 02:25 PM - "MM/DD/YYYY hh:mm:ss A", // Example: 12/25/2023 02:25:45 PM - - // Two-digit year with time - "M/D/YY h:mm A", // Example: 1/5/23 9:00 AM - "M/D/YY h:mmA", // Example: 1/5/23 9:00AM - "M/D/YY h A", // Example: 1/5/23 9 AM - "M/D/YY hA", // Example: 1/5/23 9AM - "M/D/YY hh:mm A", // Example: 1/5/23 02:25 PM - "M/D/YY hh:mm:ss A", // Example: 1/5/23 02:25:45 PM - - "MM/D/YY h:mm A", // Example: 12/5/23 9:00 AM - "MM/D/YY h:mmA", // Example: 12/5/23 9:00AM - "MM/D/YY h A", // Example: 12/5/23 9 AM - "MM/D/YY hA", // Example: 12/5/23 9AM - "MM/D/YY hh:mm A", // Example: 12/5/23 02:25 PM - "MM/D/YY hh:mm:ss A", // Example: 12/5/23 02:25:45 PM - - "M/DD/YY h:mm A", // Example: 1/25/23 9:00 AM - "M/DD/YY h:mmA", // Example: 1/25/23 9:00AM - "M/DD/YY h A", // Example: 1/25/23 9 AM - "M/DD/YY hA", // Example: 1/25/23 9AM - "M/DD/YY hh:mm A", // Example: 1/25/23 02:25 PM - "M/DD/YY hh:mm:ss A", // Example: 1/25/23 02:25:45 PM - - "MM/DD/YY h:mm A", // Example: 12/25/23 9:00 AM - "MM/DD/YY h:mmA", // Example: 12/25/23 9:00AM - "MM/DD/YY h A", // Example: 12/25/23 9 AM - "MM/DD/YY hA", // Example: 12/25/23 9AM - "MM/DD/YY hh:mm A", // Example: 12/25/23 02:25 PM - "MM/DD/YY hh:mm:ss A", // Example: 12/25/23 02:25:45 PM - - // Four-digit year without time - "M/D/YYYY", // Example: 1/5/2023 - "MM/D/YYYY", // Example: 12/5/2023 - "M/DD/YYYY", // Example: 1/25/2023 - "MM/DD/YYYY", // Example: 12/25/2023 - - // Two-digit year without time - "M/D/YY", // Example: 1/5/23 - "MM/D/YY", // Example: 12/5/23 - "M/DD/YY", // Example: 1/25/23 - "MM/DD/YY" // Example: 12/25/23 -]; - -// CONFIRMED -export const dateFormats = [ +const dateFormats = [ "MMDDYYYY", "MMDDYY", - // Four-digit year - "M/D/YYYY", // Example: 1/5/2023 - "MM/D/YYYY", // Example: 12/5/2023 - "M/DD/YYYY", // Example: 1/25/2023 - "MM/DD/YYYY", // Example: 12/25/2023 - - // Two-digit year - "M/D/YY", // Example: 1/5/23 - "MM/D/YY", // Example: 12/5/23 - "M/DD/YY", // Example: 1/25/23 - "MM/DD/YY", // Example: 12/25/23 - - // Explicitly handle single-digit month and day - "M/D/YY", // Example: 1/5/23 - "M/D/YYYY", // Example: 1/5/2023 - "M/DD/YY", // Example: 1/25/23 - "M/DD/YYYY", // Example: 1/25/2023 - "MM/D/YY", // Example: 12/5/23 - "MM/D/YYYY" // Example: 12/5/2023 + "M/D/YYYY", + "MM/D/YYYY", + "M/DD/YYYY", + "MM/DD/YYYY", + "M/D/YY", + "MM/D/YY", + "M/DD/YY", + "MM/DD/YY" ]; + +const timeFormats = ["h:mm A", "h:mmA", "h A", "hA", "hh:mm A", "hh:mm:ss A"]; + +const dateTimeFormats = [ + ...["M/D/YYYY", "MM/D/YYYY", "M/DD/YYYY", "MM/DD/YYYY", "M/D/YY", "MM/D/YY", "M/DD/YY", "MM/DD/YY"].flatMap( + (dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`) + ), + + ...["MMDDYYYY", "MMDDYY"].flatMap((dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)), + + "M/D/YYYY", + "MM/D/YYYY", + "M/DD/YYYY", + "MM/DD/YYYY", + "M/D/YY", + "MM/D/YY", + "M/DD/YY", + "MM/DD/YY", + "MMDDYYYY", + "MMDDYY" +]; + +const sanitizeInput = (input) => + input + .trim() + .toUpperCase() + .replace(/\s*(am|pm)\s*/i, " $1") + .replaceAll(".", "/") + .replaceAll("-", "/"); + +export const fuzzyMatchDate = (dateString) => { + const sanitizedInput = sanitizeInput(dateString); + + for (const format of dateFormats) { + const parsedDate = dayjs(sanitizedInput, format, true); + if (parsedDate.isValid()) { + return parsedDate; + } + } + + for (const format of dateTimeFormats) { + const parsedDateTime = dayjs(sanitizedInput, format, true); + if (parsedDateTime.isValid()) { + return parsedDateTime; // Return the dayjs object + } + } + + return null; // If no matching format is found +}; From b04ae8494146efc5ed2d27c2324398a9d5de84d1 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 23 Aug 2024 23:56:37 -0700 Subject: [PATCH 06/24] IO-2890 Update Time Signed-off-by: Allan Carr --- hasura/metadata/cron_triggers.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasura/metadata/cron_triggers.yaml b/hasura/metadata/cron_triggers.yaml index 1d051c2ae..1518ea457 100644 --- a/hasura/metadata/cron_triggers.yaml +++ b/hasura/metadata/cron_triggers.yaml @@ -1,6 +1,6 @@ - name: Kaizen Data Pump webhook: '{{HASURA_API_URL}}/data/kaizen' - schedule: 30 6 * * * + schedule: 30 5 * * * include_in_metadata: true payload: {} headers: From 68d02648d7e3accfaaf194151dce9ffa68bea88c Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 28 Aug 2024 16:15:32 -0700 Subject: [PATCH 07/24] IO-2890 Gate if environment isn't Production Signed-off-by: Allan Carr --- server/data/kaizen.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/data/kaizen.js b/server/data/kaizen.js index 1dccef61c..1670988f3 100644 --- a/server/data/kaizen.js +++ b/server/data/kaizen.js @@ -31,6 +31,12 @@ const ftpSetup = { }; exports.default = async (req, res) => { + // Only process if in production environment. + if (process.env.NODE_ENV !== "production") { + res.sendStatus(403); + return; + } + //Query for the List of Bodyshop Clients. logger.log("kaizen-start", "DEBUG", "api", null, null); const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"]; From 2a28855e4b80e333930f1d57036a60396d9c2f8d Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 29 Aug 2024 08:47:59 -0700 Subject: [PATCH 08/24] IO-2892 Gate for non-production environment Signed-off-by: Allan Carr --- server/data/autohouse.js | 6 ++++++ server/data/claimscorp.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/server/data/autohouse.js b/server/data/autohouse.js index 6b5f30af9..fa52679df 100644 --- a/server/data/autohouse.js +++ b/server/data/autohouse.js @@ -31,6 +31,12 @@ const ftpSetup = { }; exports.default = async (req, res) => { + // Only process if in production environment. + if (process.env.NODE_ENV !== "production") { + res.sendStatus(403); + return; + } + //Query for the List of Bodyshop Clients. logger.log("autohouse-start", "DEBUG", "api", null, null); const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); diff --git a/server/data/claimscorp.js b/server/data/claimscorp.js index fb3012b7a..6ebc63f81 100644 --- a/server/data/claimscorp.js +++ b/server/data/claimscorp.js @@ -31,6 +31,12 @@ const ftpSetup = { }; exports.default = async (req, res) => { + // Only process if in production environment. + if (process.env.NODE_ENV !== "production") { + res.sendStatus(403); + return; + } + //Query for the List of Bodyshop Clients. logger.log("claimscorp-start", "DEBUG", "api", null, null); const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS); From ad46ea74c054e8a16987a04165d94a039b3c52df Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 29 Aug 2024 13:03:30 -0700 Subject: [PATCH 09/24] IO-2901 Production Board List config If production_config=[] then board crashes as it can't find production_config[0] Signed-off-by: Allan Carr --- .../production-list-table.component.jsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/src/components/production-list-table/production-list-table.component.jsx b/client/src/components/production-list-table/production-list-table.component.jsx index 279e26b4a..ea3d9c2a2 100644 --- a/client/src/components/production-list-table/production-list-table.component.jsx +++ b/client/src/components/production-list-table/production-list-table.component.jsx @@ -1,23 +1,23 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; -import { Button, Dropdown, Input, Space, Statistic, Table } from "antd"; +import { SyncOutlined } from "@ant-design/icons"; import { PageHeader } from "@ant-design/pro-layout"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { Button, Dropdown, Input, Space, Statistic, Table } from "antd"; +import _ from "lodash"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import ReactDragListView from "react-drag-listview"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import Prompt from "../../utils/prompt.js"; +import AlertComponent from "../alert/alert.component.jsx"; import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component"; import ProductionListColumns from "../production-list-columns/production-list-columns.data"; import ProductionListDetail from "../production-list-detail/production-list-detail.component"; +import { ProductionListConfigManager } from "./production-list-config-manager.component.jsx"; import ProductionListPrint from "./production-list-print.component"; import ResizeableTitle from "./production-list-table.resizeable.component"; -import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import { SyncOutlined } from "@ant-design/icons"; -import Prompt from "../../utils/prompt.js"; -import _ from "lodash"; -import AlertComponent from "../alert/alert.component.jsx"; -import { ProductionListConfigManager } from "./production-list-config-manager.component.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -43,7 +43,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici const initialStateRef = useRef( (bodyshop.production_config && bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) || - bodyshop.production_config[0]?.columns.tableState || { + (bodyshop.production_config && bodyshop.production_config[0]?.columns.tableState) || { sortedInfo: {}, filteredInfo: { text: "" } } From 517d8f4163c297ed20978bc30efd06330d50d91f Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 29 Aug 2024 13:39:21 -0700 Subject: [PATCH 10/24] IO-2893 Editing Shift Tickets Signed-off-by: Allan Carr --- .../time-ticket-list.component.jsx | 129 +++++++++--------- 1 file changed, 67 insertions(+), 62 deletions(-) diff --git a/client/src/components/time-ticket-list/time-ticket-list.component.jsx b/client/src/components/time-ticket-list/time-ticket-list.component.jsx index 6385aa787..73de57e33 100644 --- a/client/src/components/time-ticket-list/time-ticket-list.component.jsx +++ b/client/src/components/time-ticket-list/time-ticket-list.component.jsx @@ -1,6 +1,6 @@ import { EditFilled, SyncOutlined } from "@ant-design/icons"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { Button, Card, Checkbox, Space, Table } from "antd"; -import dayjs from "../../utils/day"; import React, { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -10,10 +10,10 @@ import { setModalContext } from "../../redux/modals/modals.actions"; import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; import { onlyUnique } from "../../utils/arrayHelper"; +import dayjs from "../../utils/day"; import { alphaSort, dateSort } from "../../utils/sorters"; import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component"; -import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -206,56 +206,56 @@ export function TimeTicketList({ return null; } } - }, - + } ]), - { - title: t("timetickets.fields.created_by"), - dataIndex: "created_by", - key: "created_by", - sorter: (a, b) => alphaSort(a.created_by, b.created_by), - sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order, - render: (text, record) => record.created_by - }, - // { - // title: "Pay", - // dataIndex: "pay", - // key: "pay", - // render: (text, record) => - // Dinero({ amount: Math.round(record.rate * 100) }) - // .multiply(record.flat_rate ? record.productivehrs : record.actualhrs) - // .toFormat("$0.00"), - // }, - { - title: t("general.labels.actions"), - dataIndex: "actions", - key: "actions", - render: (text, record) => ( - - {techConsole && ( - - - - )} - {!techConsole && ( - { - return
; - }} - > - alphaSort(a.created_by, b.created_by), + sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order, + render: (text, record) => record.created_by + }, + // { + // title: "Pay", + // dataIndex: "pay", + // key: "pay", + // render: (text, record) => + // Dinero({ amount: Math.round(record.rate * 100) }) + // .multiply(record.flat_rate ? record.productivehrs : record.actualhrs) + // .toFormat("$0.00"), + // }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + + {techConsole && ( + + + + )} + {!techConsole && ( + { + return
; + }} + > + - - - - )} - - ) - } + : HasRbacAccess({ + bodyshop, + authLevel: authLevel, + action: "timetickets:shiftedit" + }) + ? disabled + : !record.jobid + } + > + + + + )} + + ) + } ]; const handleTableChange = (pagination, filters, sorter) => { From c994eaaa8e452fc22a763cda7a9d5c54ac824ee6 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 29 Aug 2024 13:48:39 -0700 Subject: [PATCH 11/24] IO-2893 Correct RBACs for editing tickets Signed-off-by: Allan Carr --- .../time-ticket-list.component.jsx | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/client/src/components/time-ticket-list/time-ticket-list.component.jsx b/client/src/components/time-ticket-list/time-ticket-list.component.jsx index 73de57e33..11ec514fe 100644 --- a/client/src/components/time-ticket-list/time-ticket-list.component.jsx +++ b/client/src/components/time-ticket-list/time-ticket-list.component.jsx @@ -254,24 +254,41 @@ export function TimeTicketList({ timeticket: record }} disabled={ - record.committed_at - ? HasRbacAccess({ - bodyshop, - authLevel: authLevel, - action: "timetickets:editcommitted" - }) && - HasRbacAccess({ - bodyshop, - authLevel: authLevel, - action: "timetickets:shiftedit" - }) - : HasRbacAccess({ + record.ciecacode + ? record.committed_at + ? HasRbacAccess({ + bodyshop, + authLevel: authLevel, + action: "timetickets:editcommitted" + }) && + HasRbacAccess({ + bodyshop, + authLevel: authLevel, + action: "timetickets:edit" + }) + : HasRbacAccess({ + bodyshop, + authLevel: authLevel, + action: "timetickets:edit" + }) + : record.committed_at + ? HasRbacAccess({ + bodyshop, + authLevel: authLevel, + action: "timetickets:editcommitted" + }) && + HasRbacAccess({ bodyshop, authLevel: authLevel, action: "timetickets:shiftedit" }) - ? disabled - : !record.jobid + : HasRbacAccess({ + bodyshop, + authLevel: authLevel, + action: "timetickets:shiftedit" + }) + ? disabled + : !record.jobid } > From 1e855799f87cdf07abd16008bed03591315a2734 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 29 Aug 2024 14:59:14 -0700 Subject: [PATCH 12/24] IO-2894 Modify Shift Memo Signed-off-by: Allan Carr --- .../tech-job-print-tickets.component.jsx | 8 +++++--- .../time-ticket-list/time-ticket-list.component.jsx | 2 +- .../time-ticket-modal/time-ticket-modal.component.jsx | 8 +++----- .../time-ticket-shift-active.component.jsx | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx b/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx index 3802c4fb5..3f060089f 100644 --- a/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx +++ b/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx @@ -1,5 +1,4 @@ import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd"; -import dayjs from "../../utils/day"; import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -7,10 +6,12 @@ import { createStructuredSelector } from "reselect"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { selectTechnician } from "../../redux/tech/tech.selectors"; import DatePIckerRanges from "../../utils/DatePickerRanges"; +import dayjs from "../../utils/day"; import { GenerateDocument } from "../../utils/RenderTemplate"; import { TemplateList } from "../../utils/TemplateConstants"; const mapStateToProps = createStructuredSelector({ + bodyshop: selectTechnician, technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ @@ -18,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({ }); export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets); -export function TechJobPrintTickets({ technician, event, attendacePrint }) { +export function TechJobPrintTickets({ bodyshop, technician, event, attendacePrint }) { const { t } = useTranslation(); const [loading, setLoading] = useState(false); @@ -57,7 +58,8 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) { subject: attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject }, - values.sendby // === "email" ? "e" : "p" + values.sendby, + bodyshop ); } catch (error) { console.log(error); diff --git a/client/src/components/time-ticket-list/time-ticket-list.component.jsx b/client/src/components/time-ticket-list/time-ticket-list.component.jsx index 6385aa787..03fa28cba 100644 --- a/client/src/components/time-ticket-list/time-ticket-list.component.jsx +++ b/client/src/components/time-ticket-list/time-ticket-list.component.jsx @@ -165,7 +165,7 @@ export function TimeTicketList({ key: "memo", sorter: (a, b) => alphaSort(a.memo, b.memo), sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order, - render: (text, record) => (record.clockon || record.clockoff ? t(record.memo) : record.memo) + render: (text, record) => (record.memo.startsWith("timetickets.labels") ? t(record.memo) : record.memo) }, ...(Enhanced_Payroll.treatment === "on" ? [ diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx index 7a86d0d56..841bee38b 100644 --- a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx +++ b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx @@ -1,4 +1,5 @@ import { useLazyQuery } from "@apollo/client"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { Form, Input, InputNumber, Select, Switch } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; @@ -7,8 +8,7 @@ import { createStructuredSelector } from "reselect"; import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries"; import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component"; -import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; -import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; +import { default as DateTimePicker, default as FormDateTimePicker } from "../form-date-time-picker/form-date-time-picker.component"; import JobSearchSelect from "../job-search-select/job-search-select.component"; import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component"; import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility"; @@ -16,7 +16,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; import TimeTicketList from "../time-ticket-list/time-ticket-list.component"; -import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -71,9 +70,8 @@ export function TimeTicketModalComponent({ const MemoInput = ({ value, ...props }) => { return ( ); }; diff --git a/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx b/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx index dc67bae09..ba93d27b4 100644 --- a/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx +++ b/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx @@ -39,7 +39,7 @@ export default function TimeTicketShiftActive({ timetickets, refetch, isTechCons renderItem={(ticket) => ( Date: Thu, 29 Aug 2024 15:00:21 -0700 Subject: [PATCH 13/24] IO-2894 Prettier code Signed-off-by: Allan Carr --- .../time-ticket-modal.component.jsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx index 841bee38b..1b286f24b 100644 --- a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx +++ b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx @@ -8,7 +8,10 @@ import { createStructuredSelector } from "reselect"; import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries"; import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component"; -import { default as DateTimePicker, default as FormDateTimePicker } from "../form-date-time-picker/form-date-time-picker.component"; +import { + default as DateTimePicker, + default as FormDateTimePicker +} from "../form-date-time-picker/form-date-time-picker.component"; import JobSearchSelect from "../job-search-select/job-search-select.component"; import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component"; import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility"; @@ -68,12 +71,7 @@ export function TimeTicketModalComponent({ }; const MemoInput = ({ value, ...props }) => { - return ( - - ); + return ; }; return ( From 220f3d4410df1f7ae43c41317eb374b71fadbd08 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Sat, 31 Aug 2024 11:53:02 -0700 Subject: [PATCH 14/24] IO-2892 Correct Cron Trigger Signed-off-by: Allan Carr --- hasura/metadata/cron_triggers.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasura/metadata/cron_triggers.yaml b/hasura/metadata/cron_triggers.yaml index 39202c4a4..ff7aef8d1 100644 --- a/hasura/metadata/cron_triggers.yaml +++ b/hasura/metadata/cron_triggers.yaml @@ -13,7 +13,7 @@ payload: {} headers: - name: x-imex-auth - value: DATAPUMP_AUTH + value_from_env: DATAPUMP_AUTH - name: Kaizen Data Pump webhook: '{{HASURA_API_URL}}/data/kaizen' schedule: 30 5 * * * From 2a2d399a98d4e20c0225f6c64445fb1576c79821 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 3 Sep 2024 08:54:06 -0700 Subject: [PATCH 15/24] IO-2894 Null check Signed-off-by: Allan Carr --- .../time-ticket-list/time-ticket-list.component.jsx | 6 +++--- .../time-ticket-shift-active.component.jsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/components/time-ticket-list/time-ticket-list.component.jsx b/client/src/components/time-ticket-list/time-ticket-list.component.jsx index 03fa28cba..26d252c6a 100644 --- a/client/src/components/time-ticket-list/time-ticket-list.component.jsx +++ b/client/src/components/time-ticket-list/time-ticket-list.component.jsx @@ -1,6 +1,6 @@ import { EditFilled, SyncOutlined } from "@ant-design/icons"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { Button, Card, Checkbox, Space, Table } from "antd"; -import dayjs from "../../utils/day"; import React, { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -10,10 +10,10 @@ import { setModalContext } from "../../redux/modals/modals.actions"; import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; import { onlyUnique } from "../../utils/arrayHelper"; +import dayjs from "../../utils/day"; import { alphaSort, dateSort } from "../../utils/sorters"; import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component"; -import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -165,7 +165,7 @@ export function TimeTicketList({ key: "memo", sorter: (a, b) => alphaSort(a.memo, b.memo), sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order, - render: (text, record) => (record.memo.startsWith("timetickets.labels") ? t(record.memo) : record.memo) + render: (text, record) => (record.memo?.startsWith("timetickets.labels") ? t(record.memo) : record.memo) }, ...(Enhanced_Payroll.treatment === "on" ? [ diff --git a/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx b/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx index ba93d27b4..3401aa7a1 100644 --- a/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx +++ b/client/src/components/time-ticket-shift-active/time-ticket-shift-active.component.jsx @@ -39,7 +39,7 @@ export default function TimeTicketShiftActive({ timetickets, refetch, isTechCons renderItem={(ticket) => ( Date: Thu, 5 Sep 2024 08:17:45 -0700 Subject: [PATCH 16/24] IO-2707 resolve time ticket modal rerender issue. --- .../time-ticket-modal/time-ticket-modal.component.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx index 1b286f24b..7f2a1df72 100644 --- a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx +++ b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx @@ -329,7 +329,9 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideT timetickets={lineTicketData.timetickets} adjustments={lineTicketData.jobs_by_pk.lbr_adjustments} /> - {!hideTimeTickets && } + {!hideTimeTickets && ( + + )}
); } From 7a54b55bd46bde5a9f1ba996b62379904167a3ad Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 5 Sep 2024 08:26:49 -0700 Subject: [PATCH 17/24] IO-2724 Resolve tech console showing 2 drawers on production board. --- client/src/pages/tech-lookup/tech-lookup.container.jsx | 2 ++ client/src/pages/tech/tech.page.component.jsx | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/pages/tech-lookup/tech-lookup.container.jsx b/client/src/pages/tech-lookup/tech-lookup.container.jsx index b92a11d3f..a209777ba 100644 --- a/client/src/pages/tech-lookup/tech-lookup.container.jsx +++ b/client/src/pages/tech-lookup/tech-lookup.container.jsx @@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next"; import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component"; import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component"; export default function TechLookupContainer() { const { t } = useTranslation(); @@ -20,6 +21,7 @@ export default function TechLookupContainer() { return (
+
diff --git a/client/src/pages/tech/tech.page.component.jsx b/client/src/pages/tech/tech.page.component.jsx index 2733bae68..2be9f0a2a 100644 --- a/client/src/pages/tech/tech.page.component.jsx +++ b/client/src/pages/tech/tech.page.component.jsx @@ -9,7 +9,6 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import TechHeader from "../../components/tech-header/tech-header.component"; -import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component"; import TechSider from "../../components/tech-sider/tech-sider.component"; import UpdateAlert from "../../components/update-alert/update-alert.component"; import { selectTechnician } from "../../redux/tech/tech.selectors"; @@ -68,7 +67,7 @@ export function TechPage({ technician }) { - + From f7207a9f3faf637a1edf898a8efe7848fd845174 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 5 Sep 2024 09:17:42 -0700 Subject: [PATCH 18/24] Add missing PWA dependency for Vite. --- client/package-lock.json | 4 +++- client/package.json | 3 ++- client/src/components/update-alert/update-alert.component.jsx | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index bf0a5afcd..f794877d6 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -109,7 +109,8 @@ "vite-plugin-legacy": "^2.1.0", "vite-plugin-node-polyfills": "^0.22.0", "vite-plugin-pwa": "^0.20.1", - "vite-plugin-style-import": "^2.0.0" + "vite-plugin-style-import": "^2.0.0", + "workbox-window": "^7.1.0" }, "engines": { "node": ">=18.18.2" @@ -18429,6 +18430,7 @@ "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.1.0.tgz", "integrity": "sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==", "dev": true, + "license": "MIT", "dependencies": { "@types/trusted-types": "^2.0.2", "workbox-core": "7.1.0" diff --git a/client/package.json b/client/package.json index 42f26736d..e89aabfb0 100644 --- a/client/package.json +++ b/client/package.json @@ -153,6 +153,7 @@ "vite-plugin-legacy": "^2.1.0", "vite-plugin-node-polyfills": "^0.22.0", "vite-plugin-pwa": "^0.20.1", - "vite-plugin-style-import": "^2.0.0" + "vite-plugin-style-import": "^2.0.0", + "workbox-window": "^7.1.0" } } diff --git a/client/src/components/update-alert/update-alert.component.jsx b/client/src/components/update-alert/update-alert.component.jsx index 70159adda..b4a867052 100644 --- a/client/src/components/update-alert/update-alert.component.jsx +++ b/client/src/components/update-alert/update-alert.component.jsx @@ -31,7 +31,7 @@ export function UpdateAlert({ updateAvailable }) { () => { r.update(); }, - 10 * 60 * 1000 + 30 * 60 * 1000 ); } }, From cdcef798df724934bc7e9dad78f23c0576cf7881 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 5 Sep 2024 12:47:19 -0400 Subject: [PATCH 19/24] - Use Dinero in place of straight math in production board Signed-off-by: Dave Richer --- .../production-board-kanban.statistics.jsx | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx b/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx index 1af5ec59c..e2f5b6f08 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx @@ -3,6 +3,7 @@ import { Card, Statistic } from "antd"; import { useTranslation } from "react-i18next"; import PropTypes from "prop-types"; import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js"; +import Dinero from "dinero.js"; export const StatisticType = { HOURS: "hours", @@ -32,7 +33,31 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => { }; const calculateTotalAmount = (items, key) => { - return items.reduce((acc, item) => acc + (item[key]?.totals?.subtotal?.amount || 0), 0); + return items.reduce( + (acc, item) => { + const amountInCents = item[key]?.totals?.subtotal?.amount || 0; + const dineroAmount = Dinero({ amount: amountInCents }); + return acc.add(dineroAmount); + }, + Dinero({ amount: 0 }) + ); + }; + + const calculateReducerTotalAmount = (lanes, key) => { + return lanes.reduce( + (acc, lane) => { + const laneTotal = lane.cards.reduce( + (laneAcc, card) => { + const amountInCents = card.metadata[key]?.totals?.subtotal?.amount || 0; + const dineroAmount = Dinero({ amount: amountInCents }); + return laneAcc.add(dineroAmount); + }, + Dinero({ amount: 0 }) + ); + return acc.add(laneTotal); + }, + Dinero({ amount: 0 }) + ); }; const calculateReducerTotal = (lanes, key, subKey) => { @@ -43,14 +68,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => { }, 0); }; - const calculateReducerTotalAmount = (lanes, key) => { - return lanes.reduce((acc, lane) => { - return ( - acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata[key]?.totals?.subtotal?.amount || 0), 0) - ); - }, 0); - }; - const formatValue = (value, type) => { if (type === StatisticType.JOBS) { return value.toFixed(0); @@ -87,9 +104,15 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => { const totalAmountInProduction = useMemo(() => { if (!cardSettings.totalAmountInProduction) return null; const total = calculateTotalAmount(data, "job_totals"); - return parseFloat(total.toFixed(2)); + return total.toFormat("$0,0.00"); // Formatting the Dinero object to a string }, [data, cardSettings.totalAmountInProduction]); + const totalAmountOnBoard = useMemo(() => { + if (!reducerData || !cardSettings.totalAmountOnBoard) return null; + const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals"); + return total.toFormat("$0,0.00"); // Formatting the Dinero object to a string + }, [reducerData, cardSettings.totalAmountOnBoard]); + const totalHrsOnBoard = useMemo(() => { if (!reducerData || !cardSettings.totalHrsOnBoard) return null; const total = @@ -118,12 +141,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => { [reducerData, cardSettings.jobsOnBoard] ); - const totalAmountOnBoard = useMemo(() => { - if (!reducerData || !cardSettings.totalAmountOnBoard) return null; - const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals"); - return parseFloat(total.toFixed(2)); - }, [reducerData, cardSettings.totalAmountOnBoard]); - const tasksInProduction = useMemo(() => { if (!data || !cardSettings.tasksInProduction) return null; return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0); @@ -191,7 +208,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => { Date: Thu, 5 Sep 2024 13:38:50 -0400 Subject: [PATCH 20/24] - Use Dinero in place of straight math in production board Signed-off-by: Dave Richer --- .../production-board-kanban.statistics.jsx | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx b/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx index e2f5b6f08..f96b63903 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx @@ -32,29 +32,30 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => { return items.reduce((acc, item) => acc + (item[key]?.aggregate?.sum?.[subKey] || 0), 0); }; - const calculateTotalAmount = (items, key) => { + const sumDineroAmounts = (items, key, getAmountFn) => { return items.reduce( (acc, item) => { - const amountInCents = item[key]?.totals?.subtotal?.amount || 0; - const dineroAmount = Dinero({ amount: amountInCents }); + const amount = getAmountFn(item, key); + const dineroAmount = Dinero(amount ?? 0); return acc.add(dineroAmount); }, Dinero({ amount: 0 }) ); }; + const calculateTotalAmount = (items, key) => { + return items.reduce((acc, item) => acc.add(Dinero(item[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 })); + }; + const calculateReducerTotalAmount = (lanes, key) => { return lanes.reduce( (acc, lane) => { - const laneTotal = lane.cards.reduce( - (laneAcc, card) => { - const amountInCents = card.metadata[key]?.totals?.subtotal?.amount || 0; - const dineroAmount = Dinero({ amount: amountInCents }); - return laneAcc.add(dineroAmount); - }, - Dinero({ amount: 0 }) + return acc.add( + lane.cards.reduce( + (laneAcc, card) => laneAcc.add(Dinero(card.metadata[key]?.totals?.subtotal ?? Dinero())), + Dinero({ amount: 0 }) + ) ); - return acc.add(laneTotal); }, Dinero({ amount: 0 }) ); @@ -104,13 +105,13 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => { const totalAmountInProduction = useMemo(() => { if (!cardSettings.totalAmountInProduction) return null; const total = calculateTotalAmount(data, "job_totals"); - return total.toFormat("$0,0.00"); // Formatting the Dinero object to a string + return total.toFormat("$0,0.00"); }, [data, cardSettings.totalAmountInProduction]); const totalAmountOnBoard = useMemo(() => { if (!reducerData || !cardSettings.totalAmountOnBoard) return null; const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals"); - return total.toFormat("$0,0.00"); // Formatting the Dinero object to a string + return total.toFormat("$0,0.00"); }, [reducerData, cardSettings.totalAmountOnBoard]); const totalHrsOnBoard = useMemo(() => { From fb2bc20b4fe0a0eda86cf5b765f61649d73427fa Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 5 Sep 2024 13:59:03 -0400 Subject: [PATCH 21/24] - removed unused function Signed-off-by: Dave Richer --- .../production-board-kanban.statistics.jsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx b/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx index f96b63903..301ff17f0 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx @@ -32,17 +32,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => { return items.reduce((acc, item) => acc + (item[key]?.aggregate?.sum?.[subKey] || 0), 0); }; - const sumDineroAmounts = (items, key, getAmountFn) => { - return items.reduce( - (acc, item) => { - const amount = getAmountFn(item, key); - const dineroAmount = Dinero(amount ?? 0); - return acc.add(dineroAmount); - }, - Dinero({ amount: 0 }) - ); - }; - const calculateTotalAmount = (items, key) => { return items.reduce((acc, item) => acc.add(Dinero(item[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 })); }; From d40f3ee45a76c59533468b8313a9fc07adabc73a Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 5 Sep 2024 11:52:06 -0700 Subject: [PATCH 22/24] Add in IP tracking for SingleDeviceOnly checks. --- client/src/redux/user/user.sagas.js | 47 +++++++++++++++++++---------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/client/src/redux/user/user.sagas.js b/client/src/redux/user/user.sagas.js index 99ba30225..52aaff580 100644 --- a/client/src/redux/user/user.sagas.js +++ b/client/src/redux/user/user.sagas.js @@ -10,7 +10,7 @@ import { signInWithEmailAndPassword, signOut } from "firebase/auth"; -import { doc, getDoc, setDoc } from "firebase/firestore"; +import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "firebase/firestore"; import { getToken } from "firebase/messaging"; import i18next from "i18next"; import LogRocket from "logrocket"; @@ -48,6 +48,7 @@ import { validatePasswordResetSuccess } from "./user.actions"; import UserActionTypes from "./user.types"; +import cleanAxios from "../../utils/CleanAxios"; const fpPromise = FingerprintJS.load(); @@ -177,10 +178,24 @@ export function* setInstanceIdSaga({ payload: uid }) { // Get the visitor identifier when you need it. const fp = yield fpPromise; const result = yield fp.get(); - yield setDoc(userInstanceRef, { - timestamp: new Date(), - fingerprint: result.visitorId - }); + const res = yield cleanAxios.get("https://api.ipify.org/?format=json"); + const udoc = yield getDoc(userInstanceRef); + + if (!udoc.data()) { + yield setDoc(userInstanceRef, { + timestamp: new Date(), + fingerprint: result.visitorId, + //totalFingerprint: result, + ip: [res.data.ip] + }); + } else { + yield updateDoc(userInstanceRef, { + timestamp: new Date(), + fingerprint: result.visitorId, + //totalFingerprint: result, + ip: arrayUnion(res.data.ip) + }); + } yield put(setLocalFingerprint(result.visitorId)); yield delay(5 * 60 * 1000); @@ -311,17 +326,17 @@ export function* SetAuthLevelFromShopDetails({ payload }) { ); const user = yield select((state) => state.user.currentUser); - if (payload.features.singleDeviceOnly) { - if ( - !( - user.email.includes("@imex.") || - user.email.includes("@rome.") || - user.email.includes("@rometech.") || - user.email.includes("@promanager.") - ) - ) - yield put(setInstanceId(user.uid)); - } + // if (payload.features.singleDeviceOnly) { + // if ( + // !( + // user.email.includes("@imex.") || + // user.email.includes("@rome.") || + // user.email.includes("@rometech.") || + // user.email.includes("@promanager.") + // ) + // ) + yield put(setInstanceId(user.uid)); + //} //For Rome, check to make sure it's not a PM shop. try { From 12c75357b5b10040124266ed41dc5b8c9ff8f2ee Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 5 Sep 2024 11:53:48 -0700 Subject: [PATCH 23/24] Revert IP tracking to only single device users. --- client/src/redux/user/user.sagas.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/client/src/redux/user/user.sagas.js b/client/src/redux/user/user.sagas.js index 52aaff580..da6b93d65 100644 --- a/client/src/redux/user/user.sagas.js +++ b/client/src/redux/user/user.sagas.js @@ -326,17 +326,17 @@ export function* SetAuthLevelFromShopDetails({ payload }) { ); const user = yield select((state) => state.user.currentUser); - // if (payload.features.singleDeviceOnly) { - // if ( - // !( - // user.email.includes("@imex.") || - // user.email.includes("@rome.") || - // user.email.includes("@rometech.") || - // user.email.includes("@promanager.") - // ) - // ) - yield put(setInstanceId(user.uid)); - //} + if (payload.features.singleDeviceOnly) { + if ( + !( + user.email.includes("@imex.") || + user.email.includes("@rome.") || + user.email.includes("@rometech.") || + user.email.includes("@promanager.") + ) + ) + yield put(setInstanceId(user.uid)); + } //For Rome, check to make sure it's not a PM shop. try { From c4bab7294757d6d1be340de729cab38489e4b58b Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 5 Sep 2024 16:36:59 -0700 Subject: [PATCH 24/24] DB change to .env Signed-off-by: Allan Carr --- client/.env.development.imex | 4 ++-- client/.env.development.promanager | 4 ++-- client/.env.development.rome | 7 ++++--- hasura/config.yaml | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/client/.env.development.imex b/client/.env.development.imex index b5e6e8f53..2f2d96aaf 100644 --- a/client/.env.development.imex +++ b/client/.env.development.imex @@ -1,5 +1,5 @@ -VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql -VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql +VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql +VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql VITE_APP_GA_CODE=231099835 VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"} VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test diff --git a/client/.env.development.promanager b/client/.env.development.promanager index 15fa08bce..d870802cc 100644 --- a/client/.env.development.promanager +++ b/client/.env.development.promanager @@ -1,5 +1,5 @@ -VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql -VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql +VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql +VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql VITE_APP_GA_CODE=231099835 VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"} VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test diff --git a/client/.env.development.rome b/client/.env.development.rome index fce6ec37b..c68088a98 100644 --- a/client/.env.development.rome +++ b/client/.env.development.rome @@ -1,7 +1,8 @@ -VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql -VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql +VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql +VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql VITE_APP_GA_CODE=231099835 -VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"} +# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"} +VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"} VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test VITE_APP_CLOUDINARY_API_KEY=957865933348715 diff --git a/hasura/config.yaml b/hasura/config.yaml index fe93f9198..0a62aa7a2 100644 --- a/hasura/config.yaml +++ b/hasura/config.yaml @@ -1,5 +1,5 @@ version: 2 -endpoint: https://db.dev.bodyshop.app +endpoint: https://db.dev.imex.online admin_secret: Dev-BodyShopApp! metadata_directory: metadata actions: