Merge remote-tracking branch 'origin/release/2024-09-06' into feature/IO-2742-redis
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
VITE_APP_GRAPHQL_ENDPOINT=https://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.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||||
VITE_APP_GA_CODE=231099835
|
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_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_API=https://api.cloudinary.com/v1_1/io-test
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
VITE_APP_GRAPHQL_ENDPOINT=https://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.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||||
VITE_APP_GA_CODE=231099835
|
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_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_API=https://api.cloudinary.com/v1_1/io-test
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
VITE_APP_GRAPHQL_ENDPOINT=https://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.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||||
VITE_APP_GA_CODE=231099835
|
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_API=https://api.cloudinary.com/v1_1/io-test
|
||||||
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
||||||
VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||||
|
|||||||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@@ -109,7 +109,8 @@
|
|||||||
"vite-plugin-legacy": "^2.1.0",
|
"vite-plugin-legacy": "^2.1.0",
|
||||||
"vite-plugin-node-polyfills": "^0.22.0",
|
"vite-plugin-node-polyfills": "^0.22.0",
|
||||||
"vite-plugin-pwa": "^0.20.1",
|
"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": {
|
"engines": {
|
||||||
"node": ">=18.18.2"
|
"node": ">=18.18.2"
|
||||||
@@ -18429,6 +18430,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.1.0.tgz",
|
||||||
"integrity": "sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==",
|
"integrity": "sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/trusted-types": "^2.0.2",
|
"@types/trusted-types": "^2.0.2",
|
||||||
"workbox-core": "7.1.0"
|
"workbox-core": "7.1.0"
|
||||||
|
|||||||
@@ -153,6 +153,7 @@
|
|||||||
"vite-plugin-legacy": "^2.1.0",
|
"vite-plugin-legacy": "^2.1.0",
|
||||||
"vite-plugin-node-polyfills": "^0.22.0",
|
"vite-plugin-node-polyfills": "^0.22.0",
|
||||||
"vite-plugin-pwa": "^0.20.1",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from "prop-types";
|
|||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import dayjs from "../../utils/day";
|
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 DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||||
const [isManualInput, setIsManualInput] = useState(false);
|
const [isManualInput, setIsManualInput] = useState(false);
|
||||||
@@ -11,79 +11,34 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, is
|
|||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(newDate) => {
|
(newDate) => {
|
||||||
if (newDate === null && onChange) {
|
if (onChange) {
|
||||||
onChange(null);
|
onChange(newDate || null);
|
||||||
} else if (newDate && onChange) {
|
|
||||||
onChange(newDate);
|
|
||||||
}
|
}
|
||||||
setIsManualInput(false);
|
setIsManualInput(false);
|
||||||
},
|
},
|
||||||
[onChange]
|
[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(
|
const handleBlur = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
|
// Bail if this is not a manual input
|
||||||
if (!isManualInput) {
|
if (!isManualInput) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Reset manual input flag
|
||||||
setIsManualInput(false);
|
setIsManualInput(false);
|
||||||
|
|
||||||
const v = e.target.value;
|
const v = e?.target?.value;
|
||||||
|
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
|
|
||||||
const upperV = normalizeDateTimeString(v);
|
let parsedDate = isDateOnly ? fuzzyMatchDate(v)?.startOf("day") : fuzzyMatchDate(v);
|
||||||
let parsedDate;
|
|
||||||
|
|
||||||
for (const format of isDateOnly ? dateFormats : dateTimeFormats) {
|
if (parsedDate && onChange) {
|
||||||
parsedDate = dayjs(upperV, format);
|
onChange(parsedDate);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isManualInput, isDateOnly, onlyFuture, onChange, value]
|
[isManualInput, isDateOnly, onChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
|
|||||||
@@ -1,96 +1,63 @@
|
|||||||
export const dateTimeFormats = [
|
import dayjs from "../../utils/day";
|
||||||
// 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
|
|
||||||
|
|
||||||
"MM/D/YYYY h:mm A", // Example: 12/5/2023 9:00 AM
|
const dateFormats = [
|
||||||
"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 = [
|
|
||||||
"MMDDYYYY",
|
"MMDDYYYY",
|
||||||
"MMDDYY",
|
"MMDDYY",
|
||||||
// Four-digit year
|
"M/D/YYYY",
|
||||||
"M/D/YYYY", // Example: 1/5/2023
|
"MM/D/YYYY",
|
||||||
"MM/D/YYYY", // Example: 12/5/2023
|
"M/DD/YYYY",
|
||||||
"M/DD/YYYY", // Example: 1/25/2023
|
"MM/DD/YYYY",
|
||||||
"MM/DD/YYYY", // Example: 12/25/2023
|
"M/D/YY",
|
||||||
|
"MM/D/YY",
|
||||||
// Two-digit year
|
"M/DD/YY",
|
||||||
"M/D/YY", // Example: 1/5/23
|
"MM/DD/YY"
|
||||||
"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
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Card, Statistic } from "antd";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
|
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
|
|
||||||
export const StatisticType = {
|
export const StatisticType = {
|
||||||
HOURS: "hours",
|
HOURS: "hours",
|
||||||
@@ -32,7 +33,21 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const calculateTotalAmount = (items, key) => {
|
const calculateTotalAmount = (items, key) => {
|
||||||
return items.reduce((acc, item) => acc + (item[key]?.totals?.subtotal?.amount || 0), 0);
|
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) => {
|
||||||
|
return acc.add(
|
||||||
|
lane.cards.reduce(
|
||||||
|
(laneAcc, card) => laneAcc.add(Dinero(card.metadata[key]?.totals?.subtotal ?? Dinero())),
|
||||||
|
Dinero({ amount: 0 })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Dinero({ amount: 0 })
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateReducerTotal = (lanes, key, subKey) => {
|
const calculateReducerTotal = (lanes, key, subKey) => {
|
||||||
@@ -43,14 +58,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
}, 0);
|
}, 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) => {
|
const formatValue = (value, type) => {
|
||||||
if (type === StatisticType.JOBS) {
|
if (type === StatisticType.JOBS) {
|
||||||
return value.toFixed(0);
|
return value.toFixed(0);
|
||||||
@@ -87,9 +94,15 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
const totalAmountInProduction = useMemo(() => {
|
const totalAmountInProduction = useMemo(() => {
|
||||||
if (!cardSettings.totalAmountInProduction) return null;
|
if (!cardSettings.totalAmountInProduction) return null;
|
||||||
const total = calculateTotalAmount(data, "job_totals");
|
const total = calculateTotalAmount(data, "job_totals");
|
||||||
return parseFloat(total.toFixed(2));
|
return total.toFormat("$0,0.00");
|
||||||
}, [data, cardSettings.totalAmountInProduction]);
|
}, [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");
|
||||||
|
}, [reducerData, cardSettings.totalAmountOnBoard]);
|
||||||
|
|
||||||
const totalHrsOnBoard = useMemo(() => {
|
const totalHrsOnBoard = useMemo(() => {
|
||||||
if (!reducerData || !cardSettings.totalHrsOnBoard) return null;
|
if (!reducerData || !cardSettings.totalHrsOnBoard) return null;
|
||||||
const total =
|
const total =
|
||||||
@@ -118,12 +131,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
[reducerData, cardSettings.jobsOnBoard]
|
[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(() => {
|
const tasksInProduction = useMemo(() => {
|
||||||
if (!data || !cardSettings.tasksInProduction) return null;
|
if (!data || !cardSettings.tasksInProduction) return null;
|
||||||
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
||||||
@@ -191,7 +198,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
<Statistic
|
<Statistic
|
||||||
title={t(`production.statistics.${stat.label}`)}
|
title={t(`production.statistics.${stat.label}`)}
|
||||||
value={formatValue(stat.value, stat.type)}
|
value={formatValue(stat.value, stat.type)}
|
||||||
prefix={stat.type === StatisticType.AMOUNT ? t("production.statistics.currency_symbol") : undefined}
|
|
||||||
suffix={
|
suffix={
|
||||||
stat.type === StatisticType.HOURS
|
stat.type === StatisticType.HOURS
|
||||||
? t("production.statistics.hours")
|
? t("production.statistics.hours")
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
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 ReactDragListView from "react-drag-listview";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.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 ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
|
||||||
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
||||||
import ProductionListDetail from "../production-list-detail/production-list-detail.component";
|
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 ProductionListPrint from "./production-list-print.component";
|
||||||
import ResizeableTitle from "./production-list-table.resizeable.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({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -43,7 +43,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
const initialStateRef = useRef(
|
const initialStateRef = useRef(
|
||||||
(bodyshop.production_config &&
|
(bodyshop.production_config &&
|
||||||
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
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: {},
|
sortedInfo: {},
|
||||||
filteredInfo: { text: "" }
|
filteredInfo: { text: "" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
|
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -7,10 +6,12 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
import DatePIckerRanges from "../../utils/DatePickerRanges";
|
import DatePIckerRanges from "../../utils/DatePickerRanges";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectTechnician,
|
||||||
technician: selectTechnician
|
technician: selectTechnician
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
@@ -18,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets);
|
export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets);
|
||||||
|
|
||||||
export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
export function TechJobPrintTickets({ bodyshop, technician, event, attendacePrint }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -57,7 +58,8 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
|||||||
subject:
|
subject:
|
||||||
attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject
|
attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject
|
||||||
},
|
},
|
||||||
values.sendby // === "email" ? "e" : "p"
|
values.sendby,
|
||||||
|
bodyshop
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Card, Checkbox, Space, Table } from "antd";
|
import { Button, Card, Checkbox, Space, Table } from "antd";
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
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 { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
import { onlyUnique } from "../../utils/arrayHelper";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -165,7 +165,7 @@ export function TimeTicketList({
|
|||||||
key: "memo",
|
key: "memo",
|
||||||
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
||||||
sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
|
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"
|
...(Enhanced_Payroll.treatment === "on"
|
||||||
? [
|
? [
|
||||||
@@ -206,76 +206,98 @@ export function TimeTicketList({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
]),
|
]),
|
||||||
{
|
{
|
||||||
title: t("timetickets.fields.created_by"),
|
title: t("timetickets.fields.created_by"),
|
||||||
dataIndex: "created_by",
|
dataIndex: "created_by",
|
||||||
key: "created_by",
|
key: "created_by",
|
||||||
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
||||||
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
||||||
render: (text, record) => record.created_by
|
render: (text, record) => record.created_by
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// title: "Pay",
|
// title: "Pay",
|
||||||
// dataIndex: "pay",
|
// dataIndex: "pay",
|
||||||
// key: "pay",
|
// key: "pay",
|
||||||
// render: (text, record) =>
|
// render: (text, record) =>
|
||||||
// Dinero({ amount: Math.round(record.rate * 100) })
|
// Dinero({ amount: Math.round(record.rate * 100) })
|
||||||
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||||
// .toFormat("$0.00"),
|
// .toFormat("$0.00"),
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{techConsole && (
|
{techConsole && (
|
||||||
<TimeTicketEnterButton
|
<TimeTicketEnterButton
|
||||||
actions={{ refetch }}
|
actions={{ refetch }}
|
||||||
context={{ id: record.id, timeticket: record }}
|
context={{ id: record.id, timeticket: record }}
|
||||||
disabled={!record.job || disabled}
|
disabled={!record.job || disabled}
|
||||||
>
|
>
|
||||||
<EditFilled />
|
<EditFilled />
|
||||||
</TimeTicketEnterButton>
|
</TimeTicketEnterButton>
|
||||||
)}
|
)}
|
||||||
{!techConsole && (
|
{!techConsole && (
|
||||||
<RbacWrapper
|
<RbacWrapper
|
||||||
action="timetickets:edit"
|
action="timetickets:edit"
|
||||||
noauth={() => {
|
noauth={() => {
|
||||||
return <div />;
|
return <div />;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TimeTicketEnterButton
|
<TimeTicketEnterButton
|
||||||
actions={{ refetch }}
|
actions={{ refetch }}
|
||||||
context={{
|
context={{
|
||||||
id: record.id,
|
id: record.id,
|
||||||
timeticket: record
|
timeticket: record
|
||||||
}}
|
}}
|
||||||
disabled={
|
disabled={
|
||||||
HasRbacAccess({
|
record.ciecacode
|
||||||
bodyshop,
|
? record.committed_at
|
||||||
authLevel: authLevel,
|
? HasRbacAccess({
|
||||||
action: "timetickets:editcommitted"
|
bodyshop,
|
||||||
}) &&
|
authLevel: authLevel,
|
||||||
HasRbacAccess({
|
action: "timetickets:editcommitted"
|
||||||
bodyshop,
|
}) &&
|
||||||
authLevel: authLevel,
|
HasRbacAccess({
|
||||||
action: "timetickets:shiftedit"
|
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"
|
||||||
|
})
|
||||||
|
: HasRbacAccess({
|
||||||
|
bodyshop,
|
||||||
|
authLevel: authLevel,
|
||||||
|
action: "timetickets:shiftedit"
|
||||||
|
})
|
||||||
? disabled
|
? disabled
|
||||||
: !record.jobid
|
: !record.jobid
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<EditFilled />
|
<EditFilled />
|
||||||
</TimeTicketEnterButton>
|
</TimeTicketEnterButton>
|
||||||
</RbacWrapper>
|
</RbacWrapper>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useLazyQuery } from "@apollo/client";
|
import { useLazyQuery } from "@apollo/client";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -7,8 +8,10 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||||
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
|
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
|
||||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import {
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
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 JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
||||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||||
@@ -16,7 +19,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -69,13 +71,7 @@ export function TimeTicketModalComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MemoInput = ({ value, ...props }) => {
|
const MemoInput = ({ value, ...props }) => {
|
||||||
return (
|
return <Input value={value?.startsWith("timetickets.labels") ? t(value) : value} {...props} />;
|
||||||
<Input
|
|
||||||
value={value?.startsWith("timetickets.") ? t(value) : value}
|
|
||||||
{...props}
|
|
||||||
disabled={value?.startsWith("timetickets.") || disabled}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -333,7 +329,9 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideT
|
|||||||
timetickets={lineTicketData.timetickets}
|
timetickets={lineTicketData.timetickets}
|
||||||
adjustments={lineTicketData.jobs_by_pk.lbr_adjustments}
|
adjustments={lineTicketData.jobs_by_pk.lbr_adjustments}
|
||||||
/>
|
/>
|
||||||
{!hideTimeTickets && <TimeTicketList loading={loading} timetickets={lineTicketData.timetickets} techConsole />}
|
{!hideTimeTickets && (
|
||||||
|
<TimeTicketList loading={loading} timetickets={jobid ? lineTicketData.timetickets : []} techConsole />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function TimeTicketShiftActive({ timetickets, refetch, isTechCons
|
|||||||
renderItem={(ticket) => (
|
renderItem={(ticket) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<Card
|
<Card
|
||||||
title={t(ticket.memo)}
|
title={ticket.memo?.startsWith("timetickets.labels") ? t(ticket.memo) : ticket.memo}
|
||||||
actions={[
|
actions={[
|
||||||
<TechClockOffButton
|
<TechClockOffButton
|
||||||
jobId={ticket.jobid}
|
jobId={ticket.jobid}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function UpdateAlert({ updateAvailable }) {
|
|||||||
() => {
|
() => {
|
||||||
r.update();
|
r.update();
|
||||||
},
|
},
|
||||||
10 * 60 * 1000
|
30 * 60 * 1000
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||||
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
|
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
|
||||||
|
|
||||||
export default function TechLookupContainer() {
|
export default function TechLookupContainer() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -20,6 +21,7 @@ export default function TechLookupContainer() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<RbacWrapperComponent action="jobs:list-active">
|
<RbacWrapperComponent action="jobs:list-active">
|
||||||
|
<TechLookupJobsDrawer />
|
||||||
<TechLookupJobsList />
|
<TechLookupJobsList />
|
||||||
</RbacWrapperComponent>
|
</RbacWrapperComponent>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
|
|||||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||||
import TechHeader from "../../components/tech-header/tech-header.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 TechSider from "../../components/tech-sider/tech-sider.component";
|
||||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
@@ -68,7 +67,7 @@ export function TechPage({ technician }) {
|
|||||||
<Layout>
|
<Layout>
|
||||||
<UpdateAlert />
|
<UpdateAlert />
|
||||||
<TechHeader />
|
<TechHeader />
|
||||||
<TechLookupJobsDrawer />
|
|
||||||
<TaskUpsertModalContainer />
|
<TaskUpsertModalContainer />
|
||||||
<Content className="tech-content-container">
|
<Content className="tech-content-container">
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
signInWithEmailAndPassword,
|
signInWithEmailAndPassword,
|
||||||
signOut
|
signOut
|
||||||
} from "firebase/auth";
|
} 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 { getToken } from "firebase/messaging";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import LogRocket from "logrocket";
|
import LogRocket from "logrocket";
|
||||||
@@ -48,6 +48,7 @@ import {
|
|||||||
validatePasswordResetSuccess
|
validatePasswordResetSuccess
|
||||||
} from "./user.actions";
|
} from "./user.actions";
|
||||||
import UserActionTypes from "./user.types";
|
import UserActionTypes from "./user.types";
|
||||||
|
import cleanAxios from "../../utils/CleanAxios";
|
||||||
|
|
||||||
const fpPromise = FingerprintJS.load();
|
const fpPromise = FingerprintJS.load();
|
||||||
|
|
||||||
@@ -177,10 +178,24 @@ export function* setInstanceIdSaga({ payload: uid }) {
|
|||||||
// Get the visitor identifier when you need it.
|
// Get the visitor identifier when you need it.
|
||||||
const fp = yield fpPromise;
|
const fp = yield fpPromise;
|
||||||
const result = yield fp.get();
|
const result = yield fp.get();
|
||||||
yield setDoc(userInstanceRef, {
|
const res = yield cleanAxios.get("https://api.ipify.org/?format=json");
|
||||||
timestamp: new Date(),
|
const udoc = yield getDoc(userInstanceRef);
|
||||||
fingerprint: result.visitorId
|
|
||||||
});
|
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 put(setLocalFingerprint(result.visitorId));
|
||||||
yield delay(5 * 60 * 1000);
|
yield delay(5 * 60 * 1000);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
version: 2
|
version: 2
|
||||||
endpoint: https://db.dev.bodyshop.app
|
endpoint: https://db.dev.imex.online
|
||||||
admin_secret: Dev-BodyShopApp!
|
admin_secret: Dev-BodyShopApp!
|
||||||
metadata_directory: metadata
|
metadata_directory: metadata
|
||||||
actions:
|
actions:
|
||||||
|
|||||||
@@ -1,3 +1,27 @@
|
|||||||
|
- name: AutoHouse Data Pump
|
||||||
|
webhook: '{{HASURA_API_URL}}/data/ah'
|
||||||
|
schedule: 0 6 * * *
|
||||||
|
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 6 * * *
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: x-imex-auth
|
||||||
|
value_from_env: DATAPUMP_AUTH
|
||||||
|
- name: Kaizen Data Pump
|
||||||
|
webhook: '{{HASURA_API_URL}}/data/kaizen'
|
||||||
|
schedule: 30 5 * * *
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: x-imex-auth
|
||||||
|
value_from_env: DATAPUMP_AUTH
|
||||||
- name: Task Reminders
|
- name: Task Reminders
|
||||||
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
|
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
|
||||||
schedule: '*/15 * * * *'
|
schedule: '*/15 * * * *'
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
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.
|
//Query for the List of Bodyshop Clients.
|
||||||
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
||||||
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
|
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
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.
|
//Query for the List of Bodyshop Clients.
|
||||||
logger.log("claimscorp-start", "DEBUG", "api", null, null);
|
logger.log("claimscorp-start", "DEBUG", "api", null, null);
|
||||||
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS);
|
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS);
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
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.
|
//Query for the List of Bodyshop Clients.
|
||||||
logger.log("kaizen-start", "DEBUG", "api", null, null);
|
logger.log("kaizen-start", "DEBUG", "api", null, null);
|
||||||
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
|
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
|
||||||
@@ -56,8 +62,8 @@ exports.default = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
|
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
|
||||||
bodyshopid: bodyshop.id,
|
bodyshopid: bodyshop.id,
|
||||||
start: start ? moment(start).startOf("hours") : moment().subtract(2, "hours").startOf("hour"),
|
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
|
||||||
...(end && { end: moment(end).endOf("hours") })
|
...(end && { end: moment(end).endOf("day") })
|
||||||
});
|
});
|
||||||
|
|
||||||
const kaizenObject = {
|
const kaizenObject = {
|
||||||
@@ -176,24 +182,19 @@ exports.default = async (req, res) => {
|
|||||||
} finally {
|
} finally {
|
||||||
sftp.end();
|
sftp.end();
|
||||||
}
|
}
|
||||||
// sendServerEmail({
|
sendServerEmail({
|
||||||
// subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
||||||
// text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||||
// Uploaded: ${JSON.stringify(
|
Uploaded: ${JSON.stringify(
|
||||||
// allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||||
// null,
|
null,
|
||||||
// 2
|
2
|
||||||
// )}
|
)}
|
||||||
// `,
|
`
|
||||||
// });
|
});
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(200).json(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))}`
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -965,22 +965,17 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (job.adjustment_bottom_line) {
|
if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) {
|
||||||
const subtotal_before_adjustment = subtotal.add(Dinero({ amount: Math.round(job.adjustment_bottom_line * -100) }));
|
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
|
||||||
const percent_of_adjustment =
|
if (IsTrueOrYes(pfp["PAN"][`prt_tx_in${tyCounter}`])) {
|
||||||
Math.round(
|
//This amount is taxable for this type.
|
||||||
subtotal_before_adjustment.toUnit() /
|
taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add(
|
||||||
(job.adjustment_bottom_line > 0 ? job.adjustment_bottom_line : job.adjustment_bottom_line * -1)
|
Dinero({
|
||||||
) / 100;
|
amount: Math.round(job.adjustment_bottom_line * 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);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const remainingTaxableAmounts = taxableAmountsByTier;
|
const remainingTaxableAmounts = taxableAmountsByTier;
|
||||||
|
|||||||
Reference in New Issue
Block a user