-
+
{metadata?.suspended &&
}
{metadata?.iouparent && (
+
diff --git a/client/src/components/production-board-kanban/production-board-kanban.component.jsx b/client/src/components/production-board-kanban/production-board-kanban.component.jsx
index afd0e94e1..0c515132b 100644
--- a/client/src/components/production-board-kanban/production-board-kanban.component.jsx
+++ b/client/src/components/production-board-kanban/production-board-kanban.component.jsx
@@ -15,13 +15,13 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
-import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
+import CardColorLegend from "./production-board-kanban-card-color-legend.component.jsx";
import "./production-board-kanban.styles.scss";
import { createBoardData } from "./production-board-kanban.utils.js";
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
import cloneDeep from "lodash/cloneDeep";
import isEqual from "lodash/isEqual";
-import { defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
+import { defaultFilters, mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
const mapStateToProps = createStructuredSelector({
@@ -41,7 +41,7 @@ const mapDispatchToProps = (dispatch) => ({
function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTrail, associationSettings, statuses }) {
const [boardLanes, setBoardLanes] = useState({ lanes: [] });
- const [filter, setFilter] = useState({ search: "", employeeId: null });
+ const [filter, setFilter] = useState(defaultFilters);
const [loading, setLoading] = useState(true);
const [isMoving, setIsMoving] = useState(false);
const [orientation, setOrientation] = useState("vertical");
@@ -182,19 +182,14 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
[boardLanes, client, getCardByID, isMoving, t, insertAuditTrail]
);
- const cardSettings = useMemo(
- () =>
- associationSettings?.kanban_settings && Object.keys(associationSettings.kanban_settings).length > 0
- ? associationSettings.kanban_settings
- : defaultKanbanSettings,
- [associationSettings]
- );
+ const cardSettings = useMemo(() => {
+ const kanbanSettings = associationSettings?.kanban_settings;
+ return mergeWithDefaults(kanbanSettings);
+ }, [associationSettings]);
- const handleSettingsChange = useCallback((newSettings) => {
- setLoading(true);
- setOrientation(newSettings.orientation ? "vertical" : "horizontal");
- setLoading(false);
- }, []);
+ const handleSettingsChange = () => {
+ setFilter(defaultFilters);
+ };
if (loading) {
return
;
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 482ddb726..1af5ec59c 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
@@ -2,11 +2,13 @@ import React, { useMemo } from "react";
import { Card, Statistic } from "antd";
import { useTranslation } from "react-i18next";
import PropTypes from "prop-types";
-import { statisticsItems, defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
+import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
+
export const StatisticType = {
HOURS: "hours",
AMOUNT: "amount",
- JOBS: "jobs"
+ JOBS: "jobs",
+ TASKS: "tasks"
};
const mergeStatistics = (items, values) => {
@@ -122,6 +124,20 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
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);
+ }, [data, cardSettings.tasksInProduction]);
+
+ const tasksOnBoard = useMemo(() => {
+ if (!reducerData || !cardSettings.tasksOnBoard) return null;
+ return reducerData.lanes.reduce((acc, lane) => {
+ return (
+ acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata.tasks_aggregate?.aggregate?.count || 0), 0)
+ );
+ }, 0);
+ }, [reducerData, cardSettings.tasksOnBoard]);
+
const statistics = useMemo(
() =>
mergeStatistics(statisticsItems, [
@@ -134,7 +150,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
{ id: 6, value: totalAmountOnBoard, type: StatisticType.AMOUNT },
{ id: 7, value: totalLABOnBoard, type: StatisticType.HOURS },
{ id: 8, value: totalLAROnBoard, type: StatisticType.HOURS },
- { id: 9, value: jobsOnBoard, type: StatisticType.JOBS }
+ { id: 9, value: jobsOnBoard, type: StatisticType.JOBS },
+ { id: 10, value: tasksOnBoard, type: StatisticType.TASKS },
+ { id: 11, value: tasksInProduction, type: StatisticType.TASKS }
]),
[
totalHrs,
@@ -146,7 +164,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
totalAmountOnBoard,
totalLABOnBoard,
totalLAROnBoard,
- jobsOnBoard
+ jobsOnBoard,
+ tasksOnBoard,
+ tasksInProduction
]
);
@@ -187,37 +207,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
};
ProductionStatistics.propTypes = {
- data: PropTypes.arrayOf(
- PropTypes.shape({
- labhrs: PropTypes.object,
- larhrs: PropTypes.object,
- job_totals: PropTypes.object
- })
- ).isRequired,
- cardSettings: PropTypes.shape({
- totalHrs: PropTypes.bool,
- totalLAB: PropTypes.bool,
- totalLAR: PropTypes.bool,
- jobsInProduction: PropTypes.bool,
- totalAmountInProduction: PropTypes.bool,
- totalHrsOnBoard: PropTypes.bool,
- totalLABOnBoard: PropTypes.bool,
- totalLAROnBoard: PropTypes.bool,
- jobsOnBoard: PropTypes.bool,
- totalAmountOnBoard: PropTypes.bool,
- statisticsOrder: PropTypes.arrayOf(PropTypes.number)
- }).isRequired,
- reducerData: PropTypes.shape({
- lanes: PropTypes.arrayOf(
- PropTypes.shape({
- cards: PropTypes.arrayOf(
- PropTypes.shape({
- metadata: PropTypes.object
- })
- ).isRequired
- })
- ).isRequired
- })
+ data: PropTypes.array.isRequired,
+ cardSettings: PropTypes.object.isRequired,
+ reducerData: PropTypes.object
};
export default ProductionStatistics;
diff --git a/client/src/components/production-board-kanban/production-board-kanban.utils.js b/client/src/components/production-board-kanban/production-board-kanban.utils.js
index fed3d12db..ba3974c1a 100644
--- a/client/src/components/production-board-kanban/production-board-kanban.utils.js
+++ b/client/src/components/production-board-kanban/production-board-kanban.utils.js
@@ -29,7 +29,7 @@ const sortByParentId = (arr) => {
// Function to create board data based on statuses and jobs, with optional filtering
export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
- const { search, employeeId } = filter;
+ const { search, employeeId, alert } = filter;
const lanes = statuses.map((status) => ({
id: status,
@@ -52,6 +52,11 @@ export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
);
}
+ // Filter jobs by alert if alert filter is true
+ if (alert) {
+ filteredJobs = filteredJobs.filter((job) => job.production_vars?.alert);
+ }
+
const DataGroupedByStatus = groupBy(filteredJobs, "status");
Object.keys(DataGroupedByStatus).forEach((statusGroupKey) => {
diff --git a/client/src/components/production-board-kanban/settings/InformationSettings.jsx b/client/src/components/production-board-kanban/settings/InformationSettings.jsx
index c50f61697..3725d1ab8 100644
--- a/client/src/components/production-board-kanban/settings/InformationSettings.jsx
+++ b/client/src/components/production-board-kanban/settings/InformationSettings.jsx
@@ -18,7 +18,8 @@ const InformationSettings = ({ t }) => (
"sublets",
"partsstatus",
"estimator",
- "subtotal"
+ "subtotal",
+ "tasks"
].map((item) => (
diff --git a/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js b/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js
index ce0760637..87b364ea2 100644
--- a/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js
+++ b/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js
@@ -8,7 +8,9 @@ const statisticsItems = [
{ id: 6, name: "totalAmountOnBoard", label: "total_amount_on_board" },
{ id: 7, name: "totalLABOnBoard", label: "total_lab_on_board" },
{ id: 8, name: "totalLAROnBoard", label: "total_lar_on_board" },
- { id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" }
+ { id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" },
+ { id: 10, name: "tasksOnBoard", label: "tasks_on_board" },
+ { id: 11, name: "tasksInProduction", label: "tasks_in_production" }
];
const defaultKanbanSettings = {
@@ -23,6 +25,7 @@ const defaultKanbanSettings = {
scheduled_completion: true,
cardcolor: false,
orientation: false,
+ tasks: false,
cardSize: "small",
model_info: true,
kiosk: false,
@@ -35,6 +38,8 @@ const defaultKanbanSettings = {
totalLABOnBoard: false,
totalLAROnBoard: false,
jobsOnBoard: false,
+ tasksOnBoard: false,
+ tasksInProduction: false,
totalAmountOnBoard: true,
estimator: false,
subtotal: false,
@@ -43,4 +48,22 @@ const defaultKanbanSettings = {
selectedEstimators: []
};
-export { defaultKanbanSettings, statisticsItems };
+const defaultFilters = { search: "", employeeId: null, alert: false };
+
+const mergeWithDefaults = (settings) => {
+ // Create a new object that starts with the default settings
+ const mergedSettings = { ...defaultKanbanSettings };
+
+ // Override with the provided settings, if any
+ if (settings) {
+ for (const key in settings) {
+ if (settings.hasOwnProperty(key)) {
+ mergedSettings[key] = settings[key];
+ }
+ }
+ }
+
+ return mergedSettings;
+};
+
+export { defaultKanbanSettings, statisticsItems, mergeWithDefaults, defaultFilters };
diff --git a/client/src/components/production-board-kanban/settings/production-board-kanban.settings.component.jsx b/client/src/components/production-board-kanban/settings/production-board-kanban.settings.component.jsx
index 38b0c9350..0d78416f8 100644
--- a/client/src/components/production-board-kanban/settings/production-board-kanban.settings.component.jsx
+++ b/client/src/components/production-board-kanban/settings/production-board-kanban.settings.component.jsx
@@ -3,13 +3,15 @@ import { Button, Card, Col, Form, notification, Popover, Row, Tabs } from "antd"
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js";
-import { defaultKanbanSettings } from "./defaultKanbanSettings.js";
+import { defaultKanbanSettings, mergeWithDefaults } from "./defaultKanbanSettings.js";
import LayoutSettings from "./LayoutSettings.jsx";
import InformationSettings from "./InformationSettings.jsx";
import StatisticsSettings from "./StatisticsSettings.jsx";
import FilterSettings from "./FilterSettings.jsx";
+import PropTypes from "prop-types";
+import { isFunction } from "lodash";
-export default function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data }) {
+function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) {
const [form] = Form.useForm();
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
@@ -23,16 +25,11 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
useEffect(() => {
if (associationSettings?.kanban_settings) {
- form.setFieldsValue(associationSettings.kanban_settings);
- if (associationSettings.kanban_settings.statisticsOrder) {
- setStatisticsOrder(associationSettings.kanban_settings.statisticsOrder);
- }
- if (associationSettings.kanban_settings.selectedMdInsCos) {
- setSelectedMdInsCos(associationSettings.kanban_settings.selectedMdInsCos);
- }
- if (associationSettings.kanban_settings.selectedEstimators) {
- setSelectedEstimators(associationSettings.kanban_settings.selectedEstimators);
- }
+ const finalSettings = mergeWithDefaults(associationSettings.kanban_settings);
+ form.setFieldsValue(finalSettings);
+ setStatisticsOrder(finalSettings.statisticsOrder);
+ setSelectedMdInsCos(finalSettings.selectedMdInsCos);
+ setSelectedEstimators(finalSettings.selectedEstimators);
}
}, [form, associationSettings]);
@@ -65,6 +62,11 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
setOpen(false);
setLoading(false);
parentLoading(false);
+
+ if (onSettingsChange && isFunction(onSettingsChange)) {
+ onSettingsChange(values);
+ }
+
setHasChanges(false);
};
@@ -155,3 +157,13 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
);
}
+
+ProductionBoardKanbanSettings.propTypes = {
+ associationSettings: PropTypes.object,
+ parentLoading: PropTypes.func.isRequired,
+ bodyshop: PropTypes.object.isRequired,
+ onSettingsChange: PropTypes.func,
+ data: PropTypes.array
+};
+
+export default ProductionBoardKanbanSettings;
diff --git a/client/src/components/production-board-kanban/trello-board/controllers/Lane.jsx b/client/src/components/production-board-kanban/trello-board/controllers/Lane.jsx
index 493812840..cd30952fb 100644
--- a/client/src/components/production-board-kanban/trello-board/controllers/Lane.jsx
+++ b/client/src/components/production-board-kanban/trello-board/controllers/Lane.jsx
@@ -12,7 +12,7 @@ import { EyeInvisibleOutlined, EyeOutlined } from "@ant-design/icons";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../../../redux/user/user.selectors.js";
import { selectTechnician } from "../../../../redux/tech/tech.selectors.js";
-import ProductionBoardCard from "../../../production-board-kanban-card/production-board-kanban-card.component.jsx";
+import ProductionBoardCard from "../../production-board-kanban-card.component.jsx";
import HeightMemoryWrapper from "../components/HeightMemoryWrapper.jsx";
import SizeMemoryWrapper from "../components/SizeMemoryWrapper.jsx";
import ListComponent from "../components/ListComponent.jsx";
diff --git a/client/src/components/production-list-columns/production-list-columns.add.component.jsx b/client/src/components/production-list-columns/production-list-columns.add.component.jsx
index 1d1ea8867..24cca5537 100644
--- a/client/src/components/production-list-columns/production-list-columns.add.component.jsx
+++ b/client/src/components/production-list-columns/production-list-columns.add.component.jsx
@@ -2,7 +2,6 @@ import React from "react";
import { Button, Dropdown } from "antd";
import dataSource from "./production-list-columns.data";
import { useTranslation } from "react-i18next";
-
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectTechnician } from "../../redux/tech/tech.selectors";
@@ -10,16 +9,23 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
- //currentUser: selectCurrentUser
technician: selectTechnician,
bodyshop: selectBodyshop
});
-const mapDispatchToProps = (dispatch) => ({
- //setUserLanguage: language => dispatch(setUserLanguage(language))
-});
-export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);
-export function ProductionColumnsComponent({ columnState, technician, bodyshop, data, tableState, refetch }) {
+const mapDispatchToProps = (dispatch) => ({
+ // Add any necessary dispatch actions here
+});
+
+export function ProductionColumnsComponent({
+ columnState,
+ technician,
+ bodyshop,
+ data,
+ tableState,
+ refetch,
+ onColumnAdd
+}) {
const [columns, setColumns] = columnState;
const { t } = useTranslation();
const {
@@ -29,18 +35,26 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop,
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid
});
+
const handleAdd = (e) => {
- setColumns([
- ...columns,
- ...dataSource({
- bodyshop,
- technician,
- state: tableState,
- data,
- activeStatuses: bodyshop.md_ro_statuses.active_statuses,
- treatments: { Enhanced_Payroll }
- }).filter((i) => i.key === e.key)
- ]);
+ const newColumn = dataSource({
+ bodyshop,
+ technician,
+ state: tableState,
+ data,
+ activeStatuses: bodyshop.md_ro_statuses.active_statuses,
+ treatments: { Enhanced_Payroll }
+ }).find((i) => i.key === e.key);
+
+ if (newColumn) {
+ const updatedColumns = [...columns, newColumn];
+ setColumns(updatedColumns);
+
+ // Call the onColumnAdd function passed as a prop
+ if (onColumnAdd) {
+ onColumnAdd(newColumn);
+ }
+ }
};
const columnKeys = columns.map((i) => i.key);
@@ -76,12 +90,4 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop,
);
}
-// c.key)}
-// render={(item) => item.title}
-// onChange={(nextTargetKeys, direction, moveKeys) => {
-// setColumns(dataSource.filter((i) => nextTargetKeys.includes(i.key)));
-// }}
-// />
+export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);
diff --git a/client/src/components/production-list-columns/production-list-columns.alert.component.jsx b/client/src/components/production-list-columns/production-list-columns.alert.component.jsx
index e9a4e2d62..db219745b 100644
--- a/client/src/components/production-list-columns/production-list-columns.alert.component.jsx
+++ b/client/src/components/production-list-columns/production-list-columns.alert.component.jsx
@@ -1,6 +1,6 @@
-import { ExclamationCircleFilled } from "@ant-design/icons";
+import { ExclamationCircleFilled, PlusCircleFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
-import { Button } from "antd";
+import { Button, Popconfirm } from "antd";
import React, { useCallback } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -8,6 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
+import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({});
@@ -22,22 +23,24 @@ const mapDispatchToProps = (dispatch) => ({
)
});
-const ProductionListColumnAlert = ({ record, insertAuditTrail }) => {
+const ProductionListColumnAlert = ({ id, productionVars, refetch, insertAuditTrail }) => {
const [updateAlert] = useMutation(UPDATE_JOB);
+ const { t } = useTranslation();
const handleAlertToggle = useCallback(() => {
logImEXEvent("production_toggle_alert");
- const newAlertState = !!record.production_vars?.alert ? !record.production_vars.alert : true;
+ const newAlertState = !!productionVars?.alert ? !productionVars?.alert : true;
+ const finalProductionVars = {
+ ...productionVars,
+ alert: newAlertState
+ };
updateAlert({
variables: {
- jobId: record.id,
+ jobId: id,
job: {
- production_vars: {
- ...record.production_vars,
- alert: newAlertState
- }
+ production_vars: finalProductionVars
}
}
}).catch((err) => {
@@ -45,17 +48,26 @@ const ProductionListColumnAlert = ({ record, insertAuditTrail }) => {
});
insertAuditTrail({
- jobid: record.id,
+ jobid: id,
operation: AuditTrailMapping.alertToggle(newAlertState),
type: "alertToggle"
});
- if (record.refetch) record.refetch();
- }, [updateAlert, insertAuditTrail, record]);
+ if (refetch) refetch();
+ }, [updateAlert, insertAuditTrail, id, productionVars, refetch]);
- if (!record.production_vars?.alert) return null;
-
- return } onClick={handleAlertToggle} />;
+ return productionVars?.alert ? (
+
+ } />
+
+ ) : (
+ } onClick={handleAlertToggle} />
+ );
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnAlert);
diff --git a/client/src/components/production-list-columns/production-list-columns.data.jsx b/client/src/components/production-list-columns/production-list-columns.data.jsx
index 6738711ae..fbc9236a5 100644
--- a/client/src/components/production-list-columns/production-list-columns.data.jsx
+++ b/client/src/components/production-list-columns/production-list-columns.data.jsx
@@ -2,10 +2,13 @@ import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
import { Checkbox, Space, Tooltip } from "antd";
import i18n from "i18next";
import { Link } from "react-router-dom";
+import { setModalContext } from "../../redux/modals/modals.actions";
+import { store } from "../../redux/store";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { TimeFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
+import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import JobAltTransportChange from "../job-at-change/job-at-change.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
@@ -23,10 +26,7 @@ import ProductionListColumnPartsReceived from "./production-list-columns.partsre
import ProductionListColumnNote from "./production-list-columns.productionnote.component";
import ProductionListColumnCategory from "./production-list-columns.status.category";
import ProductionListColumnStatus from "./production-list-columns.status.component";
-import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
-import { store } from "../../redux/store";
-import { setModalContext } from "../../redux/modals/modals.actions";
-import InstanceRenderManager from "../../utils/instanceRenderMgr";
+import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component";
const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => {
const { Enhanced_Payroll } = treatments;
@@ -258,7 +258,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
{ text: "True", value: true },
{ text: "False", value: false }
],
- onFilter: (value, record) => value.includes(record.special_coverage_policy),
+ onFilter: (value, record) => value === record.special_coverage_policy,
render: (text, record) =>
},
@@ -349,7 +349,14 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
key: "alert",
sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false),
sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order,
- render: (text, record) =>
+ filters: [
+ { text: "True", value: true },
+ { text: "False", value: false }
+ ],
+ onFilter: (value, record) => value === (record.production_vars?.alert || false),
+ render: (text, record) => (
+
+ )
},
{
title: i18n.t("production.labels.note"),
@@ -370,7 +377,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
dataIndex: "tt",
key: "tt",
render: (text, record) => {
- return ;
+ return ;
}
},
{
diff --git a/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx b/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx
index d4c92220e..141ed42bb 100644
--- a/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx
+++ b/client/src/components/production-list-columns/production-list-columns.empassignment.component.jsx
@@ -148,6 +148,7 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
) : (
{
setAssignment({ operation: type });
setVisibility(true);
diff --git a/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx b/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx
index bdbb2b5a6..a5f0b0f40 100644
--- a/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx
+++ b/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx
@@ -7,6 +7,7 @@ import { Button, Form, Input, notification, Popover, Space } from "antd";
import { useTranslation } from "react-i18next";
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
+import { isFunction } from "lodash";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -16,7 +17,7 @@ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
-export function ProductionListSaveConfigButton({ columns, bodyshop, tableState }) {
+export function ProductionListSaveConfigButton({ columns, bodyshop, tableState, onSave }) {
const [updateShop] = useMutation(UPDATE_SHOP);
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
@@ -49,6 +50,9 @@ export function ProductionListSaveConfigButton({ columns, bodyshop, tableState }
});
if (!!!result.errors) {
notification["success"]({ message: t("bodyshop.successes.save") });
+ if (onSave && isFunction(onSave)) {
+ onSave();
+ }
} else {
notification["error"]({
message: t("bodyshop.errors.saving", {
diff --git a/client/src/components/production-list-table/production-list-table-view-select.component.jsx b/client/src/components/production-list-table/production-list-table-view-select.component.jsx
index 9a98ede10..f8297fe02 100644
--- a/client/src/components/production-list-table/production-list-table-view-select.component.jsx
+++ b/client/src/components/production-list-table/production-list-table-view-select.component.jsx
@@ -11,6 +11,7 @@ import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
+import { isFunction } from "lodash";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -18,7 +19,17 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
-export function ProductionListTable({ refetch, bodyshop, technician, currentUser, state, data, setColumns, setState }) {
+export function ProductionListTable({
+ refetch,
+ bodyshop,
+ technician,
+ currentUser,
+ state,
+ data,
+ setColumns,
+ setState,
+ onProfileChange
+}) {
const { t } = useTranslation();
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
const [updateShop] = useMutation(UPDATE_SHOP);
@@ -32,25 +43,25 @@ export function ProductionListTable({ refetch, bodyshop, technician, currentUser
});
const handleSelect = async (value, option) => {
- setColumns(
- bodyshop.production_config
- .filter((pc) => pc.name === value)[0]
- .columns.columnKeys.map((k) => {
- return {
- ...ProductionListColumns({
- bodyshop,
- refetch,
- technician,
- state,
- data: data,
- activeStatuses: bodyshop.md_ro_statuses.active_statuses,
- treatments: { Enhanced_Payroll }
- }).find((e) => e.key === k.key),
- width: k.width
- };
- })
- );
- setState(bodyshop.production_config.filter((pc) => pc.name === value)[0].columns.tableState);
+ const newColumns = bodyshop.production_config
+ .filter((pc) => pc.name === value)[0]
+ .columns.columnKeys.map((k) => {
+ return {
+ ...ProductionListColumns({
+ bodyshop,
+ refetch,
+ technician,
+ state,
+ data: data,
+ activeStatuses: bodyshop.md_ro_statuses.active_statuses,
+ treatments: { Enhanced_Payroll }
+ }).find((e) => e.key === k.key),
+ width: k.width
+ };
+ });
+ setColumns(newColumns);
+ const newState = bodyshop.production_config.filter((pc) => pc.name === value)[0].columns.tableState;
+ setState(newState);
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
@@ -72,6 +83,10 @@ export function ProductionListTable({ refetch, bodyshop, technician, currentUser
}
});
}
+
+ if (onProfileChange && isFunction(onProfileChange)) {
+ onProfileChange({ value, option, newColumns, newState, assoc });
+ }
};
const handleTrash = async (name) => {
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 12938829d..6e9e1f918 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,8 +1,6 @@
-import { SyncOutlined } from "@ant-design/icons";
-import { useSplitTreatments } from "@splitsoftware/splitio-react";
+import React, { useEffect, useMemo, useRef, useState } from "react";
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
-import React, { useEffect, useMemo, useState } from "react";
import ReactDragListView from "react-drag-listview";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -16,6 +14,11 @@ import ProductionListSaveConfigButton from "../production-list-save-config-butto
import ProductionListPrint from "./production-list-print.component";
import ProductionListTableViewSelect from "./production-list-table-view-select.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";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -25,6 +28,7 @@ const mapStateToProps = createStructuredSelector({
export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) {
const [searchText, setSearchText] = useState("");
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const {
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
@@ -35,10 +39,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
});
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
-
const defaultView = assoc && assoc.default_prod_list_view;
- const [state, setState] = useState(
+ const initialStateRef = useRef(
(bodyshop.production_config &&
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
bodyshop.production_config[0]?.columns.tableState || {
@@ -47,80 +50,102 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
}
);
- const { t } = useTranslation();
-
- const matchingColumnConfig = useMemo(() => {
- return bodyshop.production_config.find((p) => p.name === defaultView);
- }, [bodyshop.production_config, defaultView]);
-
- const [columns, setColumns] = useState(
- (state &&
- matchingColumnConfig &&
- matchingColumnConfig.columns.columnKeys.map((k) => {
- return {
- ...ProductionListColumns({
- bodyshop,
- refetch,
- technician,
- state,
- data,
- activeStatuses: bodyshop.md_ro_statuses.active_statuses,
- treatments: { Production_List_Status_Colors, Enhanced_Payroll }
- }).find((e) => e.key === k.key),
- width: k.width ?? 100
- };
- })) ||
- []
- );
-
- useEffect(() => {
- const newColumns =
- (state &&
- matchingColumnConfig &&
- matchingColumnConfig.columns.columnKeys.map((k) => {
+ const initialColumnsRef = useRef(
+ (initialStateRef.current &&
+ bodyshop.production_config
+ .find((p) => p.name === defaultView)
+ ?.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
- technician,
refetch,
- state,
- data: data,
+ technician,
+ state: initialStateRef.current,
+ data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
}).find((e) => e.key === k.key),
width: k.width ?? 100
};
})) ||
- [];
- setColumns(newColumns);
- // eslint-disable-next-line react-hooks/exhaustive-deps
+ []
+ );
+
+ const [state, setState] = useState(initialStateRef.current);
+ const [columns, setColumns] = useState(initialColumnsRef.current);
+
+ const { t } = useTranslation();
+
+ const matchingColumnConfig = useMemo(() => {
+ return bodyshop.production_config.find((p) => p.name === defaultView);
+ }, [bodyshop.production_config, defaultView]);
+
+ useEffect(() => {
+ const newColumns =
+ matchingColumnConfig?.columns.columnKeys.map((k) => {
+ return {
+ ...ProductionListColumns({
+ bodyshop,
+ technician,
+ refetch,
+ state,
+ data: data,
+ activeStatuses: bodyshop.md_ro_statuses.active_statuses,
+ treatments: { Production_List_Status_Colors, Enhanced_Payroll }
+ }).find((e) => e.key === k.key),
+ width: k.width ?? 100
+ };
+ }) || [];
+
+ // Only update columns if they haven't been manually changed by the user
+ if (_.isEqual(initialColumnsRef.current, columns)) {
+ setColumns(newColumns);
+ }
}, [
- //state,
matchingColumnConfig,
bodyshop,
technician,
- data
- ]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed.
+ data,
+ Enhanced_Payroll,
+ Production_List_Status_Colors,
+ refetch,
+ state,
+ columns
+ ]);
const handleTableChange = (pagination, filters, sorter) => {
- setState({
+ const newState = {
...state,
filteredInfo: filters,
sortedInfo: { columnKey: sorter.columnKey, order: sorter.order }
- });
+ };
+ if (!_.isEqual(newState, state)) {
+ setState(newState);
+ setHasUnsavedChanges(true);
+ }
};
const onDragEnd = (fromIndex, toIndex) => {
- const columnsCopy = columns.slice();
- const item = columnsCopy.splice(fromIndex, 1)[0];
- columnsCopy.splice(toIndex, 0, item);
- setColumns(columnsCopy);
+ if (fromIndex === toIndex) return;
+
+ const columnsCopy = [...columns];
+ const [movedItem] = columnsCopy.splice(fromIndex, 1);
+ columnsCopy.splice(toIndex, 0, movedItem);
+
+ if (!_.isEqual(columnsCopy, columns)) {
+ setColumns(columnsCopy);
+ setHasUnsavedChanges(true);
+ }
};
const removeColumn = (e) => {
const { key } = e;
const newColumns = columns.filter((i) => i.key !== key);
- setColumns(newColumns);
+
+ if (!_.isEqual(newColumns, columns)) {
+ setColumns(newColumns);
+ setHasUnsavedChanges(true);
+ }
};
const handleResize =
@@ -131,9 +156,21 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
...nextColumns[index],
width: size.width
};
- setColumns(nextColumns);
+
+ if (!_.isEqual(nextColumns, columns)) {
+ setColumns(nextColumns);
+ setHasUnsavedChanges(true);
+ }
};
+ const addColumn = (newColumn) => {
+ const updatedColumns = [...columns, newColumn];
+ if (!_.isEqual(updatedColumns, columns)) {
+ setColumns(updatedColumns);
+ setHasUnsavedChanges(true);
+ }
+ };
+
const headerItem = (col) => {
const menu = {
onClick: removeColumn,
@@ -152,29 +189,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
);
};
- const dataSource =
- searchText === ""
- ? data
- : data.filter(
- (j) =>
- (j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
- (j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
- (j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
- (j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
- (j.status || "").toLowerCase().includes(searchText.toLowerCase()) ||
- (j.ins_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
- (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
- (j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) ||
- (j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase())
- );
+ const resetChanges = () => {
+ setState(initialStateRef.current);
+ setColumns(initialColumnsRef.current);
+ setHasUnsavedChanges(false);
+ };
- // const handleSelectRecord = (record) => {
- // if (selected !== record.id) {
- // setSelected(record.id);
- // } else {
- // setSelected(null);
- // }
- // };
+ const filterData = (item, searchText) => {
+ const fieldsToSearch = [
+ item.ro_number,
+ item.ownr_co_nm,
+ item.ownr_fn,
+ item.ownr_ln,
+ item.status,
+ item.ins_co_nm,
+ item.clm_no,
+ item.v_model_desc,
+ item.v_make_desc
+ ];
+
+ return fieldsToSearch.some((field) => (field || "").toString().toLowerCase().includes(searchText.toLowerCase()));
+ };
+
+ const dataSource = searchText === "" ? data : data.filter((j) => filterData(j, searchText));
if (!!!columns) return No columns found.
;
@@ -186,8 +223,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
.toFixed(1);
const totalLAB = data.reduce((acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
const totalLAR = data.reduce((acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
+
return (
+
+ {hasUnsavedChanges && (
+
+ {t("general.messages.unsavedchanges")}
+
+ {t("general.actions.reset")}
+
+
+ }
+ />
+ )}
@@ -199,20 +257,37 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
}
extra={
- refetch && refetch()}>
+ {
+ refetch && refetch();
+ }}
+ >
-
-
-
+
+ {
+ setHasUnsavedChanges(false);
+ }}
+ />
{
+ initialStateRef.current = state;
+ setHasUnsavedChanges(false);
+ }}
refetch={refetch}
data={data}
/>
-
setSearchText(e.target.value)}
placeholder={t("general.labels.search")}
diff --git a/client/src/components/update-alert/update-alert.component.jsx b/client/src/components/update-alert/update-alert.component.jsx
index 909512c23..70159adda 100644
--- a/client/src/components/update-alert/update-alert.component.jsx
+++ b/client/src/components/update-alert/update-alert.component.jsx
@@ -1,7 +1,7 @@
import { AlertOutlined } from "@ant-design/icons";
import { Alert, Button, Col, Row, Space } from "antd";
import i18n from "i18next";
-import React from "react";
+import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -12,39 +12,42 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
updateAvailable: selectUpdateAvailable
});
+
const mapDispatchToProps = (dispatch) => ({
- //setUserLanguage: language => dispatch(setUserLanguage(language))
+ // setUserLanguage: language => dispatch(setUserLanguage(language))
});
-export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert);
-const intervalMS = 10 * 60 * 1000;
+
export function UpdateAlert({ updateAvailable }) {
const { t } = useTranslation();
-
const {
- offlineReady: [
- offlineReady //setOfflineReady
- ],
- needRefresh: [
- needRefresh //setNeedRefresh
- ],
+ offlineReady: [offlineReady],
+ needRefresh: [needRefresh],
updateServiceWorker
} = useRegisterSW({
onRegistered(r) {
- // eslint-disable-next-line prefer-template
- console.log("SW Registered: " + r);
- r &&
- setInterval(() => {
- r.update();
- }, intervalMS);
+ console.log("SW Registered:", r);
+ if (r) {
+ setInterval(
+ () => {
+ r.update();
+ },
+ 10 * 60 * 1000
+ );
+ }
},
onRegisterError(error) {
- console.log("SW registration error", error);
+ console.error("SW registration error", error);
}
});
- if (import.meta.env.DEV) console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`);
+ useEffect(() => {
+ if (import.meta.env.DEV) {
+ console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`);
+ }
+ }, [needRefresh, offlineReady]);
if (!needRefresh) return null;
+
return (
- {
- window.open("https://imex-online.noticeable.news/", "_blank");
- }}
- >
+ window.open("https://imex-online.noticeable.news/", "_blank")}>
{i18n.t("general.actions.viewreleasenotes")}
- {
- updateServiceWorker(true);
- }}
- >
+ updateServiceWorker(true)}>
{i18n.t("general.actions.refresh")}
@@ -93,3 +87,5 @@ export function UpdateAlert({ updateAvailable }) {
/>
);
}
+
+export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert);
diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js
index 51ff6927c..90169eccc 100644
--- a/client/src/graphql/jobs.queries.js
+++ b/client/src/graphql/jobs.queries.js
@@ -1,7 +1,7 @@
import { gql } from "@apollo/client";
export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
- query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
+ query QUERY_ALL_ACTIVE_JOBS_PAGINATED(
$offset: Int
$limit: Int
$order: [jobs_order_by!]
@@ -2465,6 +2465,11 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
export const QUERY_JOBS_IN_PRODUCTION = gql`
query QUERY_JOBS_IN_PRODUCTION {
jobs(where: { inproduction: { _eq: true } }) {
+ tasks_aggregate(where: { completed: { _eq: false }, deleted: { _eq: false } }) {
+ aggregate {
+ count
+ }
+ }
id
updated_at
comment
diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx
index 1fe0a053b..db7146e15 100644
--- a/client/src/pages/manage/manage.page.component.jsx
+++ b/client/src/pages/manage/manage.page.component.jsx
@@ -12,7 +12,6 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
//import FooterComponent from "../../components/footer/footer.component";
//Component Imports
import * as Sentry from "@sentry/react";
-import Joyride from "react-joyride";
import TestComponent from "../../components/_test/test.page";
import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
@@ -23,8 +22,6 @@ import { requestForToken } from "../../firebase/firebase.utils";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
import UpdateAlert from "../../components/update-alert/update-alert.component";
-import { setJoyRideFinished } from "../../redux/application/application.actions.js";
-import { selectEnableJoyRide, selectJoyRideSteps } from "../../redux/application/application.selectors.js";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import "./manage.page.styles.scss";
@@ -105,16 +102,12 @@ const { Content, Footer } = Layout;
const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict,
- bodyshop: selectBodyshop,
- enableJoyRide: selectEnableJoyRide,
- joyRideSteps: selectJoyRideSteps
+ bodyshop: selectBodyshop
});
-const mapDispatchToProps = (dispatch) => ({
- setJoyRideFinished: (steps) => dispatch(setJoyRideFinished(steps))
-});
+const mapDispatchToProps = (dispatch) => ({});
-export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) {
+export function Manage({ conflict, bodyshop }) {
const { t } = useTranslation();
const [chatVisible] = useState(false);
@@ -583,21 +576,6 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
- {
- if (props.action === "reset") {
- setJoyRideFinished();
- }
- }}
- />
} showDialog>
{PageContent}
diff --git a/client/src/redux/application/application.actions.js b/client/src/redux/application/application.actions.js
index 89826ea30..7c5485ac5 100644
--- a/client/src/redux/application/application.actions.js
+++ b/client/src/redux/application/application.actions.js
@@ -67,11 +67,3 @@ export const setUpdateAvailable = (isUpdateAvailable) => ({
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
payload: isUpdateAvailable
});
-export const setJoyRideSteps = (steps) => ({
- type: ApplicationActionTypes.SET_JOYRIDE_STEPS,
- payload: steps
-});
-export const setJoyRideFinished = () => ({
- type: ApplicationActionTypes.SET_JOYRIDE_FINISHED
- //payload: isUpdateAvailable,
-});
diff --git a/client/src/redux/application/application.reducer.js b/client/src/redux/application/application.reducer.js
index 62090cc1f..421403f19 100644
--- a/client/src/redux/application/application.reducer.js
+++ b/client/src/redux/application/application.reducer.js
@@ -14,9 +14,7 @@ const INITIAL_STATE = {
error: null
},
jobReadOnly: false,
- partnerVersion: null,
- enableJoyRide: false,
- joyRideSteps: []
+ partnerVersion: null
};
const applicationReducer = (state = INITIAL_STATE, action) => {
@@ -89,12 +87,6 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
case ApplicationActionTypes.SET_PROBLEM_JOBS: {
return { ...state, problemJobs: action.payload };
}
- case ApplicationActionTypes.SET_JOYRIDE_STEPS: {
- return { ...state, enableJoyRide: true, joyRideSteps: action.payload };
- }
- case ApplicationActionTypes.SET_JOYRIDE_FINISHED: {
- return { ...state, enableJoyRide: false, joyRideSteps: [] };
- }
default:
return state;
}
diff --git a/client/src/redux/application/application.selectors.js b/client/src/redux/application/application.selectors.js
index f5c1e0770..d81699522 100644
--- a/client/src/redux/application/application.selectors.js
+++ b/client/src/redux/application/application.selectors.js
@@ -22,5 +22,3 @@ export const selectJobReadOnly = createSelector([selectApplication], (applicatio
export const selectOnline = createSelector([selectApplication], (application) => application.online);
export const selectProblemJobs = createSelector([selectApplication], (application) => application.problemJobs);
export const selectUpdateAvailable = createSelector([selectApplication], (application) => application.updateAvailable);
-export const selectEnableJoyRide = createSelector([selectApplication], (application) => application.enableJoyRide);
-export const selectJoyRideSteps = createSelector([selectApplication], (application) => application.joyRideSteps);
diff --git a/client/src/redux/application/application.types.js b/client/src/redux/application/application.types.js
index 3d8e0763b..9b95dd6ee 100644
--- a/client/src/redux/application/application.types.js
+++ b/client/src/redux/application/application.types.js
@@ -12,8 +12,6 @@ const ApplicationActionTypes = {
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
- SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE",
- SET_JOYRIDE_STEPS: "SET_JOYRIDE_STEPS",
- SET_JOYRIDE_FINISHED: "SET_JOYRIDE_FINISHED"
+ SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
};
export default ApplicationActionTypes;
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index f3a95722e..c2d776fbf 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -1160,7 +1160,8 @@
"submit": "Submit",
"tryagain": "Try Again",
"view": "View",
- "viewreleasenotes": "See What's Changed"
+ "viewreleasenotes": "See What's Changed",
+ "remove_alert": "Are you sure you want to dismiss the alert?"
},
"errors": {
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
@@ -2757,7 +2758,9 @@
"total_lab_on_board": "Body Hours on Board",
"total_lar_on_board": "Refinish Hours on Board",
"total_amount_on_board": "Dollars on Board",
- "total_jobs_on_board": "Jobs on Board"
+ "total_jobs_on_board": "Jobs on Board",
+ "tasks_in_production": "Tasks in Production",
+ "tasks_on_board": "Tasks on Board"
}
},
"actions": {
@@ -2792,6 +2795,7 @@
"model_info": "Vehicle Info",
"actual_in": "Actual In",
"alert": "Alert",
+ "tasks": "Tasks",
"alertoff": "Remove alert from Job",
"alerton": "Add alert to Job",
"ats": "Alternative Transportation",
@@ -2829,7 +2833,8 @@
"sublets": "Sublets",
"totalhours": "Total Hrs ",
"touchtime": "T/T",
- "viewname": "View Name"
+ "viewname": "View Name",
+ "alerts": "Alerts"
},
"successes": {
"removed": "Job removed from production."
@@ -2845,6 +2850,9 @@
"total_lar_on_board": "Refinish Hours on Board",
"total_amount_on_board": "Dollars on Board",
"total_jobs_on_board": "Jobs on Board",
+ "tasks_in_production": "Tasks in Production",
+ "tasks_on_board": "Tasks on Board",
+ "tasks": "Tasks",
"hours": "Hours",
"currency_symbol": "$",
"jobs": "Jobs"
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 30cac520b..c2da8510a 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -1160,7 +1160,8 @@
"submit": "",
"tryagain": "",
"view": "",
- "viewreleasenotes": ""
+ "viewreleasenotes": "",
+ "remove_alert": ""
},
"errors": {
"fcm": "",
@@ -2757,7 +2758,9 @@
"total_lab_on_board": "",
"total_lar_on_board": "",
"total_amount_on_board": "",
- "total_jobs_on_board": ""
+ "total_jobs_on_board": "",
+ "tasks_in_production": "",
+ "tasks_on_board": ""
}
},
"actions": {
@@ -2792,6 +2795,7 @@
"model_info": "",
"actual_in": "",
"alert": "",
+ "tasks": "",
"alertoff": "",
"alerton": "",
"ats": "",
@@ -2829,7 +2833,8 @@
"sublets": "",
"totalhours": "",
"touchtime": "",
- "viewname": ""
+ "viewname": "",
+ "alerts": ""
},
"successes": {
"removed": ""
@@ -2845,6 +2850,9 @@
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": "",
+ "tasks_in_production": "",
+ "tasks_on_board": "",
+ "tasks": "",
"hours": "",
"currency_symbol": "",
"jobs": ""
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 967aa9003..005accb1c 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -1160,7 +1160,8 @@
"submit": "",
"tryagain": "",
"view": "",
- "viewreleasenotes": ""
+ "viewreleasenotes": "",
+ "remove_alert": ""
},
"errors": {
"fcm": "",
@@ -2757,7 +2758,9 @@
"total_lab_on_board": "",
"total_lar_on_board": "",
"total_amount_on_board": "",
- "total_jobs_on_board": ""
+ "total_jobs_on_board": "",
+ "tasks_in_production": "",
+ "tasks_on_board": ""
}
},
"actions": {
@@ -2792,6 +2795,7 @@
"model_info": "",
"actual_in": "",
"alert": "",
+ "tasks": "",
"alertoff": "",
"alerton": "",
"ats": "",
@@ -2829,7 +2833,8 @@
"sublets": "",
"totalhours": "",
"touchtime": "",
- "viewname": ""
+ "viewname": "",
+ "alerts": ""
},
"successes": {
"removed": ""
@@ -2845,6 +2850,9 @@
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": "",
+ "tasks_in_production": "",
+ "tasks_on_board": "",
+ "tasks": "",
"hours": "",
"currency_symbol": "",
"jobs": ""
diff --git a/client/src/utils/betaHandler.js b/client/src/utils/betaHandler.js
deleted file mode 100644
index 804b38b7d..000000000
--- a/client/src/utils/betaHandler.js
+++ /dev/null
@@ -1,36 +0,0 @@
-export const BETA_KEY = "betaSwitchImex";
-
-export const checkBeta = () => {
- const cookie = document.cookie.split("; ").find((row) => row.startsWith(BETA_KEY));
- return cookie ? cookie.split("=")[1] === "true" : false;
-};
-
-export const setBeta = (value) => {
- const domain = window.location.hostname.split(".").slice(-2).join(".");
- document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
-};
-
-export const handleBeta = () => {
- // If the current host name does not start with beta or test, then we don't need to do anything.
- if (window.location.hostname.startsWith("localhost")) {
- console.log("Not on beta or test, so no need to handle beta.");
- return;
- }
-
- const isBeta = checkBeta();
-
- const currentHostName = window.location.hostname;
-
- // Beta is enabled, but the current host name does start with beta.
- if (isBeta && !currentHostName.startsWith("beta")) {
- const href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
- window.location.replace(href);
- }
-
- // Beta is not enabled, but the current host name does start with beta.
- else if (!isBeta && currentHostName.startsWith("beta")) {
- const href = `${window.location.protocol}//${currentHostName.replace("beta.", "")}${window.location.pathname}${window.location.search}${window.location.hash}`;
- window.location.replace(href);
- }
-};
-export default handleBeta;
diff --git a/client/src/utils/handleBeta.js b/client/src/utils/handleBeta.js
index 804b38b7d..dcb0d18d1 100644
--- a/client/src/utils/handleBeta.js
+++ b/client/src/utils/handleBeta.js
@@ -11,26 +11,37 @@ export const setBeta = (value) => {
};
export const handleBeta = () => {
- // If the current host name does not start with beta or test, then we don't need to do anything.
if (window.location.hostname.startsWith("localhost")) {
console.log("Not on beta or test, so no need to handle beta.");
return;
}
const isBeta = checkBeta();
-
const currentHostName = window.location.hostname;
- // Beta is enabled, but the current host name does start with beta.
- if (isBeta && !currentHostName.startsWith("beta")) {
- const href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
- window.location.replace(href);
- }
+ // Determine if the host name starts with "beta" or "www.beta"
+ const isBetaHost = currentHostName.startsWith("beta.");
+ const isBetaHostWithWWW = currentHostName.startsWith("www.beta.");
- // Beta is not enabled, but the current host name does start with beta.
- else if (!isBeta && currentHostName.startsWith("beta")) {
- const href = `${window.location.protocol}//${currentHostName.replace("beta.", "")}${window.location.pathname}${window.location.search}${window.location.hash}`;
- window.location.replace(href);
+ if (isBeta) {
+ // If beta is on and we are not on a beta domain, redirect to the beta version
+ if (!isBetaHost && !isBetaHostWithWWW) {
+ const newHostName = currentHostName.startsWith("www.")
+ ? `www.beta.${currentHostName.replace(/^www\./, "")}`
+ : `beta.${currentHostName}`;
+ const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
+ window.location.replace(href);
+ }
+ // Otherwise, if beta is on and we're already on a beta domain, stay there
+ } else {
+ // If beta is off and we are on a beta domain, redirect to the non-beta version
+ if (isBetaHost || isBetaHostWithWWW) {
+ const newHostName = currentHostName.replace(/^www\.beta\./, "www.").replace(/^beta\./, "");
+ const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
+ window.location.replace(href);
+ }
+ // Otherwise, if beta is off and we're not on a beta domain, stay there
}
};
+
export default handleBeta;
diff --git a/client/vite.config.js b/client/vite.config.js
index 0d663686d..3187e0bdf 100644
--- a/client/vite.config.js
+++ b/client/vite.config.js
@@ -6,8 +6,6 @@ import * as url from "url";
import { defineConfig } from "vite";
import { ViteEjsPlugin } from "vite-plugin-ejs";
import eslint from "vite-plugin-eslint";
-
-//import CompressionPlugin from 'vite-plugin-compression';
import { VitePWA } from "vite-plugin-pwa";
import InstanceRenderManager from "./src/utils/instanceRenderMgr";
@@ -16,7 +14,8 @@ process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
});
const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`;
-function reactVirtualized() {
+
+function reactVirtualizedFix() {
return {
name: "flat:react-virtualized",
configResolved: async () => {
@@ -37,10 +36,7 @@ function reactVirtualized() {
export default defineConfig({
base: "/",
plugins: [
- ViteEjsPlugin((viteConfig) => {
- // viteConfig is the current Vite resolved config
- return { env: viteConfig.env };
- }),
+ ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
VitePWA({
injectRegister: "auto",
registerType: "prompt",
@@ -60,7 +56,6 @@ export default defineConfig({
description: "The ultimate bodyshop management system.",
icons: [
{
- //TODO:AIO Ensure that these are correct for Rome and IO.
src: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "favicon.png",
@@ -101,7 +96,7 @@ export default defineConfig({
gcm_sender_id: "103953800507"
}
}),
- reactVirtualized(),
+ reactVirtualizedFix(),
react(),
eslint()
// CompressionPlugin(), //Cloudfront already compresses assets, so not needed.
diff --git a/hasura/migrations/1722990947416_add_op20_to_masterdata/down.sql b/hasura/migrations/1722990947416_add_op20_to_masterdata/down.sql
new file mode 100644
index 000000000..c9f184e41
--- /dev/null
+++ b/hasura/migrations/1722990947416_add_op20_to_masterdata/down.sql
@@ -0,0 +1,9 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- UPDATE "public"."masterdata"
+-- SET value = jsonb_set(
+-- value::jsonb,
+-- '{OP20}',
+-- '{"desc": "REMOVE AND REINSTALL", "opcode": "OP20", "partcode": "PAE"}'::jsonb,
+-- true
+-- );
diff --git a/hasura/migrations/1722990947416_add_op20_to_masterdata/up.sql b/hasura/migrations/1722990947416_add_op20_to_masterdata/up.sql
new file mode 100644
index 000000000..583e21e05
--- /dev/null
+++ b/hasura/migrations/1722990947416_add_op20_to_masterdata/up.sql
@@ -0,0 +1,7 @@
+UPDATE "public"."masterdata"
+SET value = jsonb_set(
+ value::jsonb,
+ '{OP20}',
+ '{"desc": "REMOVE AND REINSTALL", "opcode": "OP20", "partcode": "PAE"}'::jsonb,
+ true
+);
diff --git a/package-lock.json b/package-lock.json
index f13315d20..4bcd97b21 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,12 +9,12 @@
"version": "0.2.0",
"license": "UNLICENSED",
"dependencies": {
- "@aws-sdk/client-secrets-manager": "^3.624.0",
- "@aws-sdk/client-ses": "^3.624.0",
- "@aws-sdk/credential-provider-node": "^3.624.0",
+ "@aws-sdk/client-secrets-manager": "^3.632.0",
+ "@aws-sdk/client-ses": "^3.632.0",
+ "@aws-sdk/credential-provider-node": "^3.632.0",
"@opensearch-project/opensearch": "^2.11.0",
"aws4": "^1.13.1",
- "axios": "^1.7.3",
+ "axios": "^1.7.4",
"better-queue": "^3.8.12",
"bluebird": "^3.7.2",
"body-parser": "^1.20.2",
@@ -28,7 +28,7 @@
"dinero.js": "^1.9.1",
"dotenv": "^16.4.5",
"express": "^4.19.2",
- "firebase-admin": "^12.3.0",
+ "firebase-admin": "^12.3.1",
"graphql": "^16.9.0",
"graphql-request": "^6.1.0",
"graylog2": "^0.2.1",
@@ -42,12 +42,12 @@
"node-mailjet": "^6.0.5",
"node-persist": "^4.0.3",
"nodemailer": "^6.9.14",
- "phone": "^3.1.49",
+ "phone": "^3.1.50",
"recursive-diff": "^1.0.9",
"rimraf": "^6.0.1",
"soap": "^1.1.1",
"socket.io": "^4.7.5",
- "ssh2-sftp-client": "^11.0.0",
+ "ssh2-sftp-client": "^10.0.3",
"twilio": "^4.23.0",
"uuid": "^10.0.0",
"xml2js": "^0.6.2",
@@ -60,8 +60,8 @@
"source-map-explorer": "^2.5.2"
},
"engines": {
- "node": ">=20.15.0",
- "npm": ">=10.7.0"
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
}
},
"node_modules/@aws-crypto/sha256-browser": {
@@ -180,24 +180,24 @@
}
},
"node_modules/@aws-sdk/client-secrets-manager": {
- "version": "3.624.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.624.0.tgz",
- "integrity": "sha512-sW4eT+OVhfMTTB9Ke5tAz8/1gZmJ4G40z9Pvm4fJYRopIMIkHSeSQKTo5urX0APYZ3fdKs2Hxo22MKIZAO4kmw==",
+ "version": "3.632.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.632.0.tgz",
+ "integrity": "sha512-WsQhPHHK1yPfALcP1B7nBSGDzky6vFTUEXnUdfzb5Xy2cT+JTBTS6ChtQGqqOuGHDP/3t/9soqZ+L6rUCYBb/Q==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/client-sso-oidc": "3.624.0",
- "@aws-sdk/client-sts": "3.624.0",
- "@aws-sdk/core": "3.624.0",
- "@aws-sdk/credential-provider-node": "3.624.0",
+ "@aws-sdk/client-sso-oidc": "3.632.0",
+ "@aws-sdk/client-sts": "3.632.0",
+ "@aws-sdk/core": "3.629.0",
+ "@aws-sdk/credential-provider-node": "3.632.0",
"@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0",
- "@aws-sdk/middleware-user-agent": "3.620.0",
+ "@aws-sdk/middleware-user-agent": "3.632.0",
"@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0",
- "@aws-sdk/util-endpoints": "3.614.0",
+ "@aws-sdk/util-endpoints": "3.632.0",
"@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5",
@@ -245,24 +245,24 @@
}
},
"node_modules/@aws-sdk/client-ses": {
- "version": "3.624.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.624.0.tgz",
- "integrity": "sha512-CIi5tbQQ3wI4MBYUNZDti0xkj5SonZ+l/Bc4eBxqJK61tQB45T68SSigdMBr9fICTQ067WMYvvOtTvq9PqV4jA==",
+ "version": "3.632.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.632.0.tgz",
+ "integrity": "sha512-hi01MPJF55LEK7NB1LZrqUV7b5GyjH08EToYuekFvQf9aNoR5mqWuMEDQ/dFAowYhUa2KqCdn67HnPn0ySQxHg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/client-sso-oidc": "3.624.0",
- "@aws-sdk/client-sts": "3.624.0",
- "@aws-sdk/core": "3.624.0",
- "@aws-sdk/credential-provider-node": "3.624.0",
+ "@aws-sdk/client-sso-oidc": "3.632.0",
+ "@aws-sdk/client-sts": "3.632.0",
+ "@aws-sdk/core": "3.629.0",
+ "@aws-sdk/credential-provider-node": "3.632.0",
"@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0",
- "@aws-sdk/middleware-user-agent": "3.620.0",
+ "@aws-sdk/middleware-user-agent": "3.632.0",
"@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0",
- "@aws-sdk/util-endpoints": "3.614.0",
+ "@aws-sdk/util-endpoints": "3.632.0",
"@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5",
@@ -298,21 +298,21 @@
}
},
"node_modules/@aws-sdk/client-sso": {
- "version": "3.624.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.624.0.tgz",
- "integrity": "sha512-EX6EF+rJzMPC5dcdsu40xSi2To7GSvdGQNIpe97pD9WvZwM9tRNQnNM4T6HA4gjV1L6Jwk8rBlG/CnveXtLEMw==",
+ "version": "3.632.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.632.0.tgz",
+ "integrity": "sha512-iYWHiKBz44m3chCFvtvHnvCpL2rALzyr1e6tOZV3dLlOKtQtDUlPy6OtnXDu4y+wyJCniy8ivG3+LAe4klzn1Q==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "3.624.0",
+ "@aws-sdk/core": "3.629.0",
"@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0",
- "@aws-sdk/middleware-user-agent": "3.620.0",
+ "@aws-sdk/middleware-user-agent": "3.632.0",
"@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0",
- "@aws-sdk/util-endpoints": "3.614.0",
+ "@aws-sdk/util-endpoints": "3.632.0",
"@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5",
@@ -347,22 +347,22 @@
}
},
"node_modules/@aws-sdk/client-sso-oidc": {
- "version": "3.624.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.624.0.tgz",
- "integrity": "sha512-Ki2uKYJKKtfHxxZsiMTOvJoVRP6b2pZ1u3rcUb2m/nVgBPUfLdl8ZkGpqE29I+t5/QaS/sEdbn6cgMUZwl+3Dg==",
+ "version": "3.632.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.632.0.tgz",
+ "integrity": "sha512-Oh1fIWaoZluihOCb/zDEpRTi+6an82fgJz7fyRBugyLhEtDjmvpCQ3oKjzaOhoN+4EvXAm1ZS/ZgpvXBlIRTgw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "3.624.0",
- "@aws-sdk/credential-provider-node": "3.624.0",
+ "@aws-sdk/core": "3.629.0",
+ "@aws-sdk/credential-provider-node": "3.632.0",
"@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0",
- "@aws-sdk/middleware-user-agent": "3.620.0",
+ "@aws-sdk/middleware-user-agent": "3.632.0",
"@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0",
- "@aws-sdk/util-endpoints": "3.614.0",
+ "@aws-sdk/util-endpoints": "3.632.0",
"@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5",
@@ -396,27 +396,27 @@
"node": ">=16.0.0"
},
"peerDependencies": {
- "@aws-sdk/client-sts": "^3.624.0"
+ "@aws-sdk/client-sts": "^3.632.0"
}
},
"node_modules/@aws-sdk/client-sts": {
- "version": "3.624.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.624.0.tgz",
- "integrity": "sha512-k36fLZCb2nfoV/DKK3jbRgO/Yf7/R80pgYfMiotkGjnZwDmRvNN08z4l06L9C+CieazzkgRxNUzyppsYcYsQaw==",
+ "version": "3.632.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.632.0.tgz",
+ "integrity": "sha512-Ss5cBH09icpTvT+jtGGuQlRdwtO7RyE9BF4ZV/CEPATdd9whtJt4Qxdya8BUnkWR7h5HHTrQHqai3YVYjku41A==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/client-sso-oidc": "3.624.0",
- "@aws-sdk/core": "3.624.0",
- "@aws-sdk/credential-provider-node": "3.624.0",
+ "@aws-sdk/client-sso-oidc": "3.632.0",
+ "@aws-sdk/core": "3.629.0",
+ "@aws-sdk/credential-provider-node": "3.632.0",
"@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0",
- "@aws-sdk/middleware-user-agent": "3.620.0",
+ "@aws-sdk/middleware-user-agent": "3.632.0",
"@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0",
- "@aws-sdk/util-endpoints": "3.614.0",
+ "@aws-sdk/util-endpoints": "3.632.0",
"@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5",
@@ -451,13 +451,14 @@
}
},
"node_modules/@aws-sdk/core": {
- "version": "3.624.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.624.0.tgz",
- "integrity": "sha512-WyFmPbhRIvtWi7hBp8uSFy+iPpj8ccNV/eX86hwF4irMjfc/FtsGVIAeBXxXM/vGCjkdfEzOnl+tJ2XACD4OXg==",
+ "version": "3.629.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.629.0.tgz",
+ "integrity": "sha512-+/ShPU/tyIBM3oY1cnjgNA/tFyHtlWq+wXF9xEKRv19NOpYbWQ+xzNwVjGq8vR07cCRqy/sDQLWPhxjtuV/FiQ==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/core": "^2.3.2",
"@smithy/node-config-provider": "^3.1.4",
+ "@smithy/property-provider": "^3.1.3",
"@smithy/protocol-http": "^4.1.0",
"@smithy/signature-v4": "^4.1.0",
"@smithy/smithy-client": "^3.1.12",
@@ -506,15 +507,15 @@
}
},
"node_modules/@aws-sdk/credential-provider-ini": {
- "version": "3.624.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.624.0.tgz",
- "integrity": "sha512-mMoNIy7MO2WTBbdqMyLpbt6SZpthE6e0GkRYpsd0yozPt0RZopcBhEh+HG1U9Y1PVODo+jcMk353vAi61CfnhQ==",
+ "version": "3.632.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.632.0.tgz",
+ "integrity": "sha512-m6epoW41xa1ajU5OiHcmQHoGVtrbXBaRBOUhlCLZmcaqMLYsboM4iD/WZP8aatKEON5tTnVXh/4StV8D/+wemw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/credential-provider-env": "3.620.1",
"@aws-sdk/credential-provider-http": "3.622.0",
"@aws-sdk/credential-provider-process": "3.620.1",
- "@aws-sdk/credential-provider-sso": "3.624.0",
+ "@aws-sdk/credential-provider-sso": "3.632.0",
"@aws-sdk/credential-provider-web-identity": "3.621.0",
"@aws-sdk/types": "3.609.0",
"@smithy/credential-provider-imds": "^3.2.0",
@@ -527,20 +528,20 @@
"node": ">=16.0.0"
},
"peerDependencies": {
- "@aws-sdk/client-sts": "^3.624.0"
+ "@aws-sdk/client-sts": "^3.632.0"
}
},
"node_modules/@aws-sdk/credential-provider-node": {
- "version": "3.624.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.624.0.tgz",
- "integrity": "sha512-vYyGK7oNpd81BdbH5IlmQ6zfaQqU+rPwsKTDDBeLRjshtrGXOEpfoahVpG9PX0ibu32IOWp4ZyXBNyVrnvcMOw==",
+ "version": "3.632.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.632.0.tgz",
+ "integrity": "sha512-cL8fuJWm/xQBO4XJPkeuZzl3XinIn9EExWgzpG48NRMKR5us1RI/ucv7xFbBBaG+r/sDR2HpYBIA3lVIpm1H3Q==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/credential-provider-env": "3.620.1",
"@aws-sdk/credential-provider-http": "3.622.0",
- "@aws-sdk/credential-provider-ini": "3.624.0",
+ "@aws-sdk/credential-provider-ini": "3.632.0",
"@aws-sdk/credential-provider-process": "3.620.1",
- "@aws-sdk/credential-provider-sso": "3.624.0",
+ "@aws-sdk/credential-provider-sso": "3.632.0",
"@aws-sdk/credential-provider-web-identity": "3.621.0",
"@aws-sdk/types": "3.609.0",
"@smithy/credential-provider-imds": "^3.2.0",
@@ -570,12 +571,12 @@
}
},
"node_modules/@aws-sdk/credential-provider-sso": {
- "version": "3.624.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.624.0.tgz",
- "integrity": "sha512-A02bayIjU9APEPKr3HudrFHEx0WfghoSPsPopckDkW7VBqO4wizzcxr75Q9A3vNX+cwg0wCN6UitTNe6pVlRaQ==",
+ "version": "3.632.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.632.0.tgz",
+ "integrity": "sha512-P/4wB6j7ym5QCPTL2xlMfvf2NcXSh+z0jmsZP4WW/tVwab4hvgabPPbLeEZDSWZ0BpgtxKGvRq0GSHuGeirQbA==",
"license": "Apache-2.0",
"dependencies": {
- "@aws-sdk/client-sso": "3.624.0",
+ "@aws-sdk/client-sso": "3.632.0",
"@aws-sdk/token-providers": "3.614.0",
"@aws-sdk/types": "3.609.0",
"@smithy/property-provider": "^3.1.3",
@@ -649,13 +650,13 @@
}
},
"node_modules/@aws-sdk/middleware-user-agent": {
- "version": "3.620.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz",
- "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==",
+ "version": "3.632.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.632.0.tgz",
+ "integrity": "sha512-yY/sFsHKwG9yzSf/DTclqWJaGPI2gPBJDCGBujSqTG1zlS7Ot4fqi91DZ6088BFWzbOorDzJFcAhAEFzc6LuQg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "3.609.0",
- "@aws-sdk/util-endpoints": "3.614.0",
+ "@aws-sdk/util-endpoints": "3.632.0",
"@smithy/protocol-http": "^4.1.0",
"@smithy/types": "^3.3.0",
"tslib": "^2.6.2"
@@ -712,9 +713,10 @@
}
},
"node_modules/@aws-sdk/util-endpoints": {
- "version": "3.614.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz",
- "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==",
+ "version": "3.632.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.632.0.tgz",
+ "integrity": "sha512-LlYMU8pAbcEQphOpE6xaNLJ8kPGhklZZTVzZVpVW477NaaGgoGTMYNXTABYHcxeF5E2lLrxql9OmVpvr8GWN8Q==",
+ "license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "3.609.0",
"@smithy/types": "^3.3.0",
@@ -2425,11 +2427,12 @@
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
},
"node_modules/@types/node": {
- "version": "20.11.7",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.7.tgz",
- "integrity": "sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A==",
+ "version": "22.3.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.3.0.tgz",
+ "integrity": "sha512-nrWpWVaDZuaVc5X84xJ0vNrLvomM205oQyLsRt7OHNZbSHslcWsvgFR7O7hire2ZonjLrWBbedmotmIlJDVd6g==",
+ "license": "MIT",
"dependencies": {
- "undici-types": "~5.26.4"
+ "undici-types": "~6.18.2"
}
},
"node_modules/@types/qs": {
@@ -2693,9 +2696,9 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.7.3",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz",
- "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==",
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
+ "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -4105,15 +4108,15 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/firebase-admin": {
- "version": "12.3.0",
- "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.3.0.tgz",
- "integrity": "sha512-AKJcFbOZ7W8Fwcqh6Ba7FThXVoXwPdsf+E9vyjk5Z1vN1Z9mnTw88EQWfIsR91YglQ0KvWu1rvMhW65bcB4sog==",
+ "version": "12.3.1",
+ "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.3.1.tgz",
+ "integrity": "sha512-vEr3s3esl8nPIA9r/feDT4nzIXCfov1CyyCSpMQWp6x63Q104qke0MEGZlrHUZVROtl8FLus6niP/M9I1s4VBA==",
"license": "Apache-2.0",
"dependencies": {
"@fastify/busboy": "^3.0.0",
"@firebase/database-compat": "^1.0.2",
"@firebase/database-types": "^1.0.0",
- "@types/node": "^20.10.3",
+ "@types/node": "^22.0.1",
"farmhash-modern": "^1.1.0",
"jsonwebtoken": "^9.0.0",
"jwks-rsa": "^3.1.0",
@@ -5795,9 +5798,10 @@
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"node_modules/phone": {
- "version": "3.1.49",
- "resolved": "https://registry.npmjs.org/phone/-/phone-3.1.49.tgz",
- "integrity": "sha512-S+rHWXSQrllK5eQwz0sDbwfxQ2PzennWPgsP/jdpEPH3k7P5IBJZYjvYfU8e/RF5AwKCgOtzbTGTGJcBSLJVVw==",
+ "version": "3.1.50",
+ "resolved": "https://registry.npmjs.org/phone/-/phone-3.1.50.tgz",
+ "integrity": "sha512-TRmb2bX3sX+rrOrc8FRd8hmy4exoH2Lu3vjBP/dLgwwci1lv7DbjJ2iHMe7X4Hm8Pa0rJcfqTbq/O1vjU4NgxQ==",
+ "license": "MIT",
"engines": {
"node": ">=12"
}
@@ -6627,17 +6631,16 @@
}
},
"node_modules/ssh2-sftp-client": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-11.0.0.tgz",
- "integrity": "sha512-lOjgNYtioYquhtgyHwPryFNhllkuENjvCKkUXo18w/Q4UpEffCnEUBfiOTlwFdKIhG1rhrOGnA6DeKPSF2CP6w==",
- "license": "Apache-2.0",
+ "version": "10.0.3",
+ "resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-10.0.3.tgz",
+ "integrity": "sha512-Wlhasz/OCgrlqC8IlBZhF19Uw/X/dHI8ug4sFQybPE+0sDztvgvDf7Om6o7LbRLe68E7XkFZf3qMnqAvqn1vkQ==",
"dependencies": {
"concat-stream": "^2.0.0",
"promise-retry": "^2.0.1",
"ssh2": "^1.15.0"
},
"engines": {
- "node": ">=18.20.4"
+ "node": ">=16.20.2"
},
"funding": {
"type": "individual",
@@ -7160,9 +7163,10 @@
}
},
"node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ "version": "6.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.18.2.tgz",
+ "integrity": "sha512-5ruQbENj95yDYJNS3TvcaxPMshV7aizdv/hWYjGIKoANWKjhWNBsr2YEuYZKodQulB1b8l7ILOuDQep3afowQQ==",
+ "license": "MIT"
},
"node_modules/universalify": {
"version": "0.1.2",
diff --git a/package.json b/package.json
index 2938403bb..b9817b242 100644
--- a/package.json
+++ b/package.json
@@ -3,8 +3,8 @@
"version": "0.2.0",
"license": "UNLICENSED",
"engines": {
- "node": ">=20.15.0",
- "npm": ">=10.7.0"
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
},
"scripts": {
"setup": "rm -rf node_modules && npm i && cd client && rm -rf node_modules && npm i",
@@ -19,12 +19,12 @@
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
},
"dependencies": {
- "@aws-sdk/client-secrets-manager": "^3.624.0",
- "@aws-sdk/client-ses": "^3.624.0",
- "@aws-sdk/credential-provider-node": "^3.624.0",
+ "@aws-sdk/client-secrets-manager": "^3.632.0",
+ "@aws-sdk/client-ses": "^3.632.0",
+ "@aws-sdk/credential-provider-node": "^3.632.0",
"@opensearch-project/opensearch": "^2.11.0",
"aws4": "^1.13.1",
- "axios": "^1.7.3",
+ "axios": "^1.7.4",
"better-queue": "^3.8.12",
"bluebird": "^3.7.2",
"body-parser": "^1.20.2",
@@ -38,7 +38,7 @@
"dinero.js": "^1.9.1",
"dotenv": "^16.4.5",
"express": "^4.19.2",
- "firebase-admin": "^12.3.0",
+ "firebase-admin": "^12.3.1",
"graphql": "^16.9.0",
"graphql-request": "^6.1.0",
"graylog2": "^0.2.1",
@@ -52,12 +52,12 @@
"node-mailjet": "^6.0.5",
"node-persist": "^4.0.3",
"nodemailer": "^6.9.14",
- "phone": "^3.1.49",
+ "phone": "^3.1.50",
"recursive-diff": "^1.0.9",
"rimraf": "^6.0.1",
"soap": "^1.1.1",
"socket.io": "^4.7.5",
- "ssh2-sftp-client": "^11.0.0",
+ "ssh2-sftp-client": "^10.0.3",
"twilio": "^4.23.0",
"uuid": "^10.0.0",
"xml2js": "^0.6.2",
diff --git a/server/accounting/qb-receivables-lines.js b/server/accounting/qb-receivables-lines.js
index 5300cc822..0dedb4472 100644
--- a/server/accounting/qb-receivables-lines.js
+++ b/server/accounting/qb-receivables-lines.js
@@ -30,8 +30,14 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
if (jobline.db_ref === "936007") {
hasMashLine = true;
}
+
+ //Check if the line is a Towing Line and flag as such.
+ let isTowingLine = false;
+ if (jobline.db_ref === "936001" && jobline.line_desc.includes("Towing")) {
+ isTowingLine = true;
+ }
//Parts Lines Mappings.
- if (jobline.profitcenter_part) {
+ if (!isTowingLine && jobline.profitcenter_part) {
//TODO:AIO This appears to be a net 0 change exept for default quantity as 0 instead of 1 for imex. Need to verify.
const discountAmount =
((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) || (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) &&
diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js
index 2be6e4c12..27523c78e 100644
--- a/server/accounting/qbo/qbo-payables.js
+++ b/server/accounting/qbo/qbo-payables.js
@@ -273,7 +273,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
return result && result.json && result.json.Bill;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
- error: (error && error.authResponse && error.authResponse.body) || (error && error.message),
+ error: error, //(error && error.authResponse && error.authResponse.body) || (error && error.message),
method: "InsertBill"
});
throw error;
diff --git a/server/data/kaizen.js b/server/data/kaizen.js
index 60e307956..1dccef61c 100644
--- a/server/data/kaizen.js
+++ b/server/data/kaizen.js
@@ -33,7 +33,7 @@ const ftpSetup = {
exports.default = async (req, res) => {
//Query for the List of Bodyshop Clients.
logger.log("kaizen-start", "DEBUG", "api", null, null);
- const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE"];
+ const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, {
imexshopid: kaizenShopsIDs