Compare commits

..

1 Commits

Author SHA1 Message Date
Allan Carr
9f354f5e36 IO-2869 Production Board Alert
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-07 15:42:22 -07:00
30 changed files with 432 additions and 600 deletions

View File

@@ -31,11 +31,3 @@
These allow users to turn fields on or off, turning them all off will show the card in the most minimal form
### Statistics
- The statistics section allows users to see accumulations of both jobs on the board, and jobs in production.
- you can click a statistic to turn it on and off, and drag and drop the statistics to rearrange them
### Filters
- Allows you to set, and persist filters for estimators and insurance companies

View File

@@ -1,174 +1,174 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
<link rel="icon" href="/favicon.png" />
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
<link rel="icon" href="/ro-favicon.png" />
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
<link rel="icon" href="/pm/pm-favicon.ico" />
<% } %>
<head>
<meta charset="utf-8" />
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
<link rel="icon" href="/favicon.png" />
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
<link rel="icon" href="/ro-favicon.png" />
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
<link rel="icon" href="/pm/pm-favicon.ico" />
<% } %>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#1690ff" />
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<!-- TODO:AIo Update the individual logos for each.-->
<link rel="apple-touch-icon" href="public/logo192.png" />
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#1690ff" />
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<!-- TODO:AIo Update the individual logos for each.-->
<link rel="apple-touch-icon" href="public/logo192.png" />
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
<meta name="description" content="ImEX Online" />
<title>ImEX Online</title>
<script type="text/javascript">
window.$crisp = [];
window.CRISP_WEBSITE_ID = '36724f62-2eb0-4b29-9cdd-9905fb99913e';
(function () {
d = document;
s = d.createElement('script');
s.src = 'https://client.crisp.chat/l.js';
s.async = 1;
d.getElementsByTagName('head')[0].appendChild(s);
})();
</script>
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
<meta name="description" content="Rome Online" />
<title>Rome Online</title>
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
<meta name="description" content="ImEX Online" />
<title>ImEX Online</title>
<script type="text/javascript">
window.$crisp = [];
window.CRISP_WEBSITE_ID = '36724f62-2eb0-4b29-9cdd-9905fb99913e';
(function () {
d = document;
s = d.createElement('script');
s.src = 'https://client.crisp.chat/l.js';
s.async = 1;
d.getElementsByTagName('head')[0].appendChild(s);
})();
</script>
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
<meta name="description" content="Rome Online" />
<title>Rome Online</title>
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001 party="LiveChat528346"></call-us-selector>
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001 party="LiveChat528346"></call-us-selector>
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
<!--<call-us
phonesystem-url=https://rometech.east.3cx.us:5001
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
id="wp-live-chat-by-3CX"
minimized="true"
animation-style="noanimation"
party="LiveChat528346"
minimized-style="bubbleright"
allow-call="true"
allow-video="false"
allow-soundnotifications="true"
enable-mute="true"
enable-onmobile="true"
offline-enabled="true"
enable="true"
ignore-queueownership="false"
authentication="both"
show-operator-actual-name="true"
aknowledge-received="true"
gdpr-enabled="false"
message-userinfo-format="name"
message-dateformat="both"
lang="browser"
button-icon-type="default"
greeting-visibility="none"
greeting-offline-visibility="none"
chat-delay="2000"
enable-direct-call="true"
enable-ga="false"
></call-us>-->
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js id="tcx-callus-js" charset="utf-8"></script>
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
<!--<call-us
phonesystem-url=https://rometech.east.3cx.us:5001
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
id="wp-live-chat-by-3CX"
minimized="true"
animation-style="noanimation"
party="LiveChat528346"
minimized-style="bubbleright"
allow-call="true"
allow-video="false"
allow-soundnotifications="true"
enable-mute="true"
enable-onmobile="true"
offline-enabled="true"
enable="true"
ignore-queueownership="false"
authentication="both"
show-operator-actual-name="true"
aknowledge-received="true"
gdpr-enabled="false"
message-userinfo-format="name"
message-dateformat="both"
lang="browser"
button-icon-type="default"
greeting-visibility="none"
greeting-offline-visibility="none"
chat-delay="2000"
enable-direct-call="true"
enable-ga="false"
></call-us>-->
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js id="tcx-callus-js" charset="utf-8"></script>
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
<title>ProManager</title>
<meta name="description" content="ProManager" />
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
<title>ProManager</title>
<meta name="description" content="ProManager" />
<% } %>
<script>
!(function () {
'use strict';
var e = [
'debug',
'destroy',
'do',
'help',
'identify',
'is',
'off',
'on',
'ready',
'render',
'reset',
'safe',
'set',
];
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
else {
var n = (window.noticeable = window.noticeable || []);
function t(e) {
return function () {
var t = Array.prototype.slice.call(arguments);
return t.unshift(e), n.push(t), n;
};
}
!(function () {
for (var o = 0; o < e.length; o++) {
var r = e[o];
n[r] = t(r);
<% } %>
<script>
!(function () {
'use strict';
var e = [
'debug',
'destroy',
'do',
'help',
'identify',
'is',
'off',
'on',
'ready',
'render',
'reset',
'safe',
'set',
];
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
else {
var n = (window.noticeable = window.noticeable || []);
function t(e) {
return function () {
var t = Array.prototype.slice.call(arguments);
return t.unshift(e), n.push(t), n;
};
}
})(),
(function () {
var e = document.createElement('script');
(e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js');
var n = document.head;
n.insertBefore(e, n.firstChild);
})();
}
})();
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
!(function () {
for (var o = 0; o < e.length; o++) {
var r = e[o];
n[r] = t(r);
}
})(),
(function () {
var e = document.createElement('script');
(e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js');
var n = document.head;
n.insertBefore(e, n.firstChild);
})();
}
})();
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="src/index.jsx"></script>
</body>
<script type="module" src="src/index.jsx"></script>
</body>
</html>

View File

@@ -161,15 +161,3 @@
.rowWithColor > td {
background-color: var(--bgColor) !important;
}
.muted-button {
color: lightgray;
border: none;
background: none;
cursor: pointer;
font-size: 16px; /* Adjust as needed */
}
.muted-button:hover {
color: darkgrey;
}

View File

@@ -1,6 +1,6 @@
import { PageHeader } from "@ant-design/pro-layout";
import { useMutation, useQuery } from "@apollo/client";
import { Button, Divider, Form, Popconfirm, Space } from "antd";
import dayjs from "../../utils/day";
import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -13,7 +13,6 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import dayjs from "../../utils/day";
import AlertComponent from "../alert/alert.component";
import BillFormContainer from "../bill-form/bill-form.container";
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
@@ -23,6 +22,7 @@ import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-galler
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import BillDetailEditReturn from "./bill-detail-edit-return.component";
import { PageHeader } from "@ant-design/pro-layout";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -153,7 +153,6 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse;
return (
<>
@@ -189,7 +188,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
}
/>
<Form form={form} onFinish={handleFinish} initialValues={transformData(data)} layout="vertical">
<BillFormContainer form={form} billEdit disabled={exported} disableInHouse={isinhouse} />
<BillFormContainer form={form} billEdit disabled={exported} />
<Divider orientation="left">{t("general.labels.media")}</Divider>
{bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery

View File

@@ -41,8 +41,7 @@ export function BillFormComponent({
job,
loadOutstandingReturns,
loadInventory,
preferredMake,
disableInHouse
preferredMake
}) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -178,7 +177,7 @@ export function BillFormComponent({
]}
>
<VendorSearchSelect
disabled={disabled || disableInHouse}
disabled={disabled}
options={vendorAutoCompleteOptions}
preferredMake={preferredMake}
onSelect={handleVendorSelect}
@@ -244,7 +243,7 @@ export function BillFormComponent({
})
]}
>
<Input disabled={disabled || disableInvNumber || disableInHouse} />
<Input disabled={disabled || disableInvNumber} />
</Form.Item>
<Form.Item
label={t("bills.fields.date")}

View File

@@ -16,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse }) {
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber }) {
const {
treatments: { Simple_Inventory }
} = useSplitTreatments({
@@ -47,7 +47,6 @@ export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableI
job={lineData ? lineData.jobs_by_pk : null}
responsibilityCenters={bodyshop.md_responsibility_centers || null}
disableInvNumber={disableInvNumber}
disableInHouse={disableInHouse}
loadOutstandingReturns={loadOutstandingReturns}
loadInventory={loadInventory}
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}

View File

@@ -3,6 +3,7 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Col, Row, notification } from "antd";
import Axios from "axios";
import _ from "lodash";
import dayjs from "../../utils/day";
import queryString from "query-string";
import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -23,8 +24,6 @@ import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selecto
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import confirmDialog from "../../utils/asyncConfirm";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
@@ -33,6 +32,7 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
import HeaderFields from "./jobs-available-supplement.headerfields";
import JobsAvailableTableComponent from "./jobs-available-table.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -580,13 +580,12 @@ function ResolveCCCLineIssues(estData, bodyshop) {
InstanceRenderManager({
executeFunction: true,
args: [],
rome: () => {
promanager: () => {
if (line.mod_lbr_ty === "LAET" || line.mod_lbr_ty === "LAUT") {
// line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`;
line.mod_lbr_ty = "LAR";
}
},
promanager: "USE_ROME"
}
});
});

View File

@@ -6,20 +6,20 @@ import {
PauseCircleOutlined
} from "@ant-design/icons";
import { Card, Col, Row, Space, Tooltip } from "antd";
import Dinero from "dinero.js";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import Dinero from "dinero.js";
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
import { ProductionListColumnAlertButton } from "../production-list-columns/production-list-columns.alert.component";
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import dayjs from "../../utils/day";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const cardColor = (ssbuckets, totalHrs) => {
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
@@ -290,22 +290,6 @@ const PartsStatusComponent = ({ metadata, cardSettings }) =>
</Col>
);
const TasksToolTip = ({ metadata, cardSettings, t }) =>
cardSettings?.tasks && (
<Col span={12}>
<EllipsesToolTip
title={`${t("production.labels.tasks")}: ${metadata.tasks_aggregate?.aggregate?.count || 0}`}
kiosk={cardSettings.kiosk}
>
{metadata.tasks_aggregate?.aggregate?.count ? (
`T: ${metadata.tasks_aggregate.aggregate.count}`
) : (
<span>T: 0</span>
)}
</EllipsesToolTip>
</Col>
);
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone }) {
const { t } = useTranslation();
const { metadata } = card;
@@ -352,15 +336,21 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
cardSettings?.production_note ||
cardSettings?.partsstatus ||
cardSettings?.estimator ||
cardSettings?.subtotal ||
cardSettings?.tasks
cardSettings?.subtotal
);
}, [cardSettings]);
const headerContent = (
<div className="header-content-container">
<div className="inner-container">
<ProductionAlert id={card.id} productionVars={metadata?.production_vars} refetch={card?.refetch} key="alert" />
<ProductionListColumnAlertButton
record={{
id: card.id,
production_vars: card?.metadata.production_vars,
refetch: card?.refetch
}}
key="alert"
/>
{metadata?.suspended && <PauseCircleOutlined className="circle-outline" key="suspended" />}
{metadata?.iouparent && (
<EllipsesToolTip
@@ -403,7 +393,6 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
employee_csr={employee_csr}
/>
<EstimatorToolTip metadata={metadata} cardSettings={cardSettings} />
<TasksToolTip metadata={metadata} cardSettings={cardSettings} t={t} />
<SubtotalTooltip metadata={metadata} cardSettings={cardSettings} t={t} />
<ActualInToolTip metadata={metadata} cardSettings={cardSettings} />
<ScheduledCompletionToolTip metadata={metadata} cardSettings={cardSettings} pastDueAlert={pastDueAlert} />

View File

@@ -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-color-legend.component.jsx";
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
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 { mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
import { defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
const mapStateToProps = createStructuredSelector({
@@ -182,10 +182,13 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
[boardLanes, client, getCardByID, isMoving, t, insertAuditTrail]
);
const cardSettings = useMemo(() => {
const kanbanSettings = associationSettings?.kanban_settings;
return mergeWithDefaults(kanbanSettings);
}, [associationSettings]);
const cardSettings = useMemo(
() =>
associationSettings?.kanban_settings && Object.keys(associationSettings.kanban_settings).length > 0
? associationSettings.kanban_settings
: defaultKanbanSettings,
[associationSettings]
);
const handleSettingsChange = useCallback((newSettings) => {
setLoading(true);

View File

@@ -2,13 +2,11 @@ import React, { useMemo } from "react";
import { Card, Statistic } from "antd";
import { useTranslation } from "react-i18next";
import PropTypes from "prop-types";
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
import { statisticsItems, defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
export const StatisticType = {
HOURS: "hours",
AMOUNT: "amount",
JOBS: "jobs",
TASKS: "tasks"
JOBS: "jobs"
};
const mergeStatistics = (items, values) => {
@@ -124,20 +122,6 @@ 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, [
@@ -150,9 +134,7 @@ 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: 10, value: tasksOnBoard, type: StatisticType.TASKS },
{ id: 11, value: tasksInProduction, type: StatisticType.TASKS }
{ id: 9, value: jobsOnBoard, type: StatisticType.JOBS }
]),
[
totalHrs,
@@ -164,9 +146,7 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
totalAmountOnBoard,
totalLABOnBoard,
totalLAROnBoard,
jobsOnBoard,
tasksOnBoard,
tasksInProduction
jobsOnBoard
]
);
@@ -207,9 +187,37 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
};
ProductionStatistics.propTypes = {
data: PropTypes.array.isRequired,
cardSettings: PropTypes.object.isRequired,
reducerData: PropTypes.object
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
})
};
export default ProductionStatistics;

View File

@@ -18,8 +18,7 @@ const InformationSettings = ({ t }) => (
"sublets",
"partsstatus",
"estimator",
"subtotal",
"tasks"
"subtotal"
].map((item) => (
<Col span={4} key={item}>
<Form.Item name={item} valuePropName="checked">

View File

@@ -8,9 +8,7 @@ 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: 10, name: "tasksOnBoard", label: "tasks_on_board" },
{ id: 11, name: "tasksInProduction", label: "tasks_in_production" }
{ id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" }
];
const defaultKanbanSettings = {
@@ -25,7 +23,6 @@ const defaultKanbanSettings = {
scheduled_completion: true,
cardcolor: false,
orientation: false,
tasks: false,
cardSize: "small",
model_info: true,
kiosk: false,
@@ -38,8 +35,6 @@ const defaultKanbanSettings = {
totalLABOnBoard: false,
totalLAROnBoard: false,
jobsOnBoard: false,
tasksOnBoard: false,
tasksInProduction: false,
totalAmountOnBoard: true,
estimator: false,
subtotal: false,
@@ -48,20 +43,4 @@ const defaultKanbanSettings = {
selectedEstimators: []
};
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 };
export { defaultKanbanSettings, statisticsItems };

View File

@@ -3,14 +3,13 @@ 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, mergeWithDefaults } from "./defaultKanbanSettings.js";
import { defaultKanbanSettings } 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";
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data }) {
export default function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data }) {
const [form] = Form.useForm();
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
@@ -24,11 +23,16 @@ function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bod
useEffect(() => {
if (associationSettings?.kanban_settings) {
const finalSettings = mergeWithDefaults(associationSettings.kanban_settings);
form.setFieldsValue(finalSettings);
setStatisticsOrder(finalSettings.statisticsOrder);
setSelectedMdInsCos(finalSettings.selectedMdInsCos);
setSelectedEstimators(finalSettings.selectedEstimators);
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);
}
}
}, [form, associationSettings]);
@@ -151,12 +155,3 @@ function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bod
</Popover>
);
}
ProductionBoardKanbanSettings.propTypes = {
associationSettings: PropTypes.object,
parentLoading: PropTypes.func.isRequired,
bodyshop: PropTypes.object.isRequired,
data: PropTypes.array
};
export default ProductionBoardKanbanSettings;

View File

@@ -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.component.jsx";
import ProductionBoardCard from "../../../production-board-kanban-card/production-board-kanban-card.component.jsx";
import HeightMemoryWrapper from "../components/HeightMemoryWrapper.jsx";
import SizeMemoryWrapper from "../components/SizeMemoryWrapper.jsx";
import ListComponent from "../components/ListComponent.jsx";

View File

@@ -2,6 +2,7 @@ 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";
@@ -9,23 +10,16 @@ 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) => ({
// Add any necessary dispatch actions here
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);
export function ProductionColumnsComponent({
columnState,
technician,
bodyshop,
data,
tableState,
refetch,
onColumnAdd
}) {
export function ProductionColumnsComponent({ columnState, technician, bodyshop, data, tableState, refetch }) {
const [columns, setColumns] = columnState;
const { t } = useTranslation();
const {
@@ -35,26 +29,18 @@ export function ProductionColumnsComponent({
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid
});
const handleAdd = (e) => {
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);
}
}
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 columnKeys = columns.map((i) => i.key);
@@ -90,4 +76,12 @@ export function ProductionColumnsComponent({
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);
// <Transfer
// dataSource={dataSource}
// titles={["Source", "Target"]}
// targetKeys={columns.map((c) => c.key)}
// render={(item) => item.title}
// onChange={(nextTargetKeys, direction, moveKeys) => {
// setColumns(dataSource.filter((i) => nextTargetKeys.includes(i.key)));
// }}
// />

View File

@@ -1,14 +1,14 @@
import { ExclamationCircleFilled, PlusCircleFilled } from "@ant-design/icons";
import { ExclamationCircleFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Popconfirm } from "antd";
import React, { useCallback } from "react";
import { Button, Dropdown } from "antd";
import React, { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
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({});
@@ -23,24 +23,23 @@ const mapDispatchToProps = (dispatch) => ({
)
});
const ProductionListColumnAlert = ({ id, productionVars, refetch, insertAuditTrail }) => {
const ProductionListColumnAlertBase = ({ record, insertAuditTrail }) => {
const [updateAlert] = useMutation(UPDATE_JOB);
const { t } = useTranslation();
const handleAlertToggle = useCallback(() => {
logImEXEvent("production_toggle_alert");
const newAlertState = !!productionVars?.alert ? !productionVars?.alert : true;
const finalProductionVars = {
...productionVars,
alert: newAlertState
};
const newAlertState = !!record.production_vars?.alert ? !record.production_vars.alert : true;
updateAlert({
variables: {
jobId: id,
jobId: record.id,
job: {
production_vars: finalProductionVars
production_vars: {
...record.production_vars,
alert: newAlertState
}
}
}
}).catch((err) => {
@@ -48,26 +47,55 @@ const ProductionListColumnAlert = ({ id, productionVars, refetch, insertAuditTra
});
insertAuditTrail({
jobid: id,
jobid: record.id,
operation: AuditTrailMapping.alertToggle(newAlertState),
type: "alertToggle"
});
if (refetch) refetch();
}, [updateAlert, insertAuditTrail, id, productionVars, refetch]);
if (record.refetch) record.refetch();
}, [updateAlert, insertAuditTrail, record]);
return productionVars?.alert ? (
<Popconfirm
title={t("general.actions.remove_alert")}
onConfirm={handleAlertToggle}
okText={t("general.labels.yes")}
cancelText={t("general.labels.no")}
>
<Button className="production-alert" icon={<ExclamationCircleFilled />} />
</Popconfirm>
) : (
<Button className="muted-button" icon={<PlusCircleFilled />} onClick={handleAlertToggle} />
const menuItems = useMemo(
() => [
{
key: "toggleAlert",
label:
record.production_vars && record.production_vars.alert
? t("production.labels.alertoff")
: t("production.labels.alerton"),
onClick: handleAlertToggle
}
],
[record.production_vars, t, handleAlertToggle]
);
return { handleAlertToggle, menuItems };
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnAlert);
export const ProductionListColumnAlertButton = connect(
mapStateToProps,
mapDispatchToProps
)(({ record, insertAuditTrail }) => {
const { handleAlertToggle } = ProductionListColumnAlertBase({ record, insertAuditTrail });
if (!record.production_vars?.alert) return null;
return <Button className="production-alert" icon={<ExclamationCircleFilled />} onClick={handleAlertToggle} />;
});
export const ProductionListColumnAlertMenu = connect(
mapStateToProps,
mapDispatchToProps
)(({ record, insertAuditTrail }) => {
const { menuItems } = ProductionListColumnAlertBase({ record, insertAuditTrail });
return (
<Dropdown menu={{ items: menuItems }} trigger={["contextMenu"]}>
<div style={{ height: "19px" }}>
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
</div>
</Dropdown>
);
});

View File

@@ -2,16 +2,19 @@ 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";
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import ProductionListColumnAlert from "./production-list-columns.alert.component";
import { ProductionListColumnAlertMenu } from "./production-list-columns.alert.component";
import ProductionListColumnBodyPriority from "./production-list-columns.bodypriority.component";
import ProductionListColumnComment from "./production-list-columns.comment.component";
import ProductionListDate from "./production-list-columns.date.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;
@@ -349,9 +349,7 @@ 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) => (
<ProductionListColumnAlert id={record.id} productionVars={record?.production_vars} refetch={refetch} />
)
render: (text, record) => <ProductionListColumnAlertMenu record={{ id: record.id, production_vars: record?.production_vars }} />
},
{
title: i18n.t("production.labels.note"),
@@ -372,7 +370,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
dataIndex: "tt",
key: "tt",
render: (text, record) => {
return <ProductionListColumnTouchTime job={record} />;
return <ProductionlistColumnTouchTime job={record} />;
}
},
{

View File

@@ -148,7 +148,6 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
) : (
<PlusCircleFilled
style={iconStyle}
className="muted-button"
onClick={() => {
setAssignment({ operation: type });
setVisibility(true);

View File

@@ -7,7 +7,6 @@ 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
@@ -17,7 +16,7 @@ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function ProductionListSaveConfigButton({ columns, bodyshop, tableState, onSave }) {
export function ProductionListSaveConfigButton({ columns, bodyshop, tableState }) {
const [updateShop] = useMutation(UPDATE_SHOP);
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
@@ -50,9 +49,6 @@ 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", {

View File

@@ -11,7 +11,6 @@ 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,
@@ -19,17 +18,7 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
export function ProductionListTable({
refetch,
bodyshop,
technician,
currentUser,
state,
data,
setColumns,
setState,
onProfileChange
}) {
export function ProductionListTable({ refetch, bodyshop, technician, currentUser, state, data, setColumns, setState }) {
const { t } = useTranslation();
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
const [updateShop] = useMutation(UPDATE_SHOP);
@@ -43,25 +32,25 @@ export function ProductionListTable({
});
const handleSelect = async (value, option) => {
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);
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 assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
@@ -83,10 +72,6 @@ export function ProductionListTable({
}
});
}
if (onProfileChange && isFunction(onProfileChange)) {
onProfileChange({ value, option, newColumns, newState, assoc });
}
};
const handleTrash = async (name) => {

View File

@@ -1,6 +1,8 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { SyncOutlined } from "@ant-design/icons";
import { useSplitTreatments } from "@splitsoftware/splitio-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";
@@ -14,11 +16,6 @@ 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,
@@ -28,7 +25,6 @@ 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 }
@@ -39,9 +35,10 @@ 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 initialStateRef = useRef(
const [state, setState] = useState(
(bodyshop.production_config &&
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
bodyshop.production_config[0]?.columns.tableState || {
@@ -50,102 +47,80 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
}
);
const initialColumnsRef = useRef(
(initialStateRef.current &&
bodyshop.production_config
.find((p) => p.name === defaultView)
?.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
refetch,
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
};
})) ||
[]
);
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) => {
const [columns, setColumns] = useState(
(state &&
matchingColumnConfig &&
matchingColumnConfig.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
technician,
refetch,
technician,
state,
data: 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);
}
useEffect(() => {
const newColumns =
(state &&
matchingColumnConfig &&
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
};
})) ||
[];
setColumns(newColumns);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
//state,
matchingColumnConfig,
bodyshop,
technician,
data,
Enhanced_Payroll,
Production_List_Status_Colors,
refetch,
state,
columns
]);
data
]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed.
const handleTableChange = (pagination, filters, sorter) => {
const newState = {
setState({
...state,
filteredInfo: filters,
sortedInfo: { columnKey: sorter.columnKey, order: sorter.order }
};
if (!_.isEqual(newState, state)) {
setState(newState);
setHasUnsavedChanges(true);
}
});
};
const onDragEnd = (fromIndex, toIndex) => {
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 columnsCopy = columns.slice();
const item = columnsCopy.splice(fromIndex, 1)[0];
columnsCopy.splice(toIndex, 0, item);
setColumns(columnsCopy);
};
const removeColumn = (e) => {
const { key } = e;
const newColumns = columns.filter((i) => i.key !== key);
if (!_.isEqual(newColumns, columns)) {
setColumns(newColumns);
setHasUnsavedChanges(true);
}
setColumns(newColumns);
};
const handleResize =
@@ -156,21 +131,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
...nextColumns[index],
width: size.width
};
if (!_.isEqual(nextColumns, columns)) {
setColumns(nextColumns);
setHasUnsavedChanges(true);
}
setColumns(nextColumns);
};
const addColumn = (newColumn) => {
const updatedColumns = [...columns, newColumn];
if (!_.isEqual(updatedColumns, columns)) {
setColumns(updatedColumns);
setHasUnsavedChanges(true);
}
};
const headerItem = (col) => {
const menu = {
onClick: removeColumn,
@@ -189,29 +152,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
);
};
const resetChanges = () => {
setState(initialStateRef.current);
setColumns(initialColumnsRef.current);
setHasUnsavedChanges(false);
};
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 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));
// const handleSelectRecord = (record) => {
// if (selected !== record.id) {
// setSelected(record.id);
// } else {
// setSelected(null);
// }
// };
if (!!!columns) return <div>No columns found.</div>;
@@ -223,29 +186,8 @@ 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 (
<div>
<Prompt when={hasUnsavedChanges} beforeUnload={true} message={t("general.messages.unsavedchangespopup")} />
{hasUnsavedChanges && (
<AlertComponent
type="warning"
message={
<div>
<span>{t("general.messages.unsavedchanges")} </span>
<span
onClick={resetChanges}
style={{
cursor: "pointer",
textDecoration: "underline"
}}
>
{t("general.actions.reset")}
</span>
</div>
}
/>
)}
<PageHeader
title={
<Space>
@@ -257,37 +199,20 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
}
extra={
<Space wrap>
<Button
onClick={() => {
refetch && refetch();
}}
>
<Button onClick={() => refetch && refetch()}>
<SyncOutlined />
</Button>
<ProductionListColumnsAdd
columnState={[columns, setColumns]}
tableState={state}
data={data}
onColumnAdd={addColumn}
/>
<ProductionListSaveConfigButton
columns={columns}
tableState={state}
onSave={() => {
setHasUnsavedChanges(false);
}}
/>
<ProductionListColumnsAdd columnState={[columns, setColumns]} tableState={state} data={data} />
<ProductionListSaveConfigButton columns={columns} tableState={state} />
<ProductionListTableViewSelect
state={state}
setState={setState}
setColumns={setColumns}
onProfileChange={() => {
initialStateRef.current = state;
setHasUnsavedChanges(false);
}}
refetch={refetch}
data={data}
/>
<Input
onChange={(e) => setSearchText(e.target.value)}
placeholder={t("general.labels.search")}

View File

@@ -2465,11 +2465,6 @@ 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

View File

@@ -1160,8 +1160,7 @@
"submit": "Submit",
"tryagain": "Try Again",
"view": "View",
"viewreleasenotes": "See What's Changed",
"remove_alert": "Are you sure you want to dismiss the alert?"
"viewreleasenotes": "See What's Changed"
},
"errors": {
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
@@ -2758,9 +2757,7 @@
"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",
"tasks_in_production": "Tasks in Production",
"tasks_on_board": "Tasks on Board"
"total_jobs_on_board": "Jobs on Board"
}
},
"actions": {
@@ -2795,7 +2792,6 @@
"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",
@@ -2849,9 +2845,6 @@
"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"

View File

@@ -1160,8 +1160,7 @@
"submit": "",
"tryagain": "",
"view": "",
"viewreleasenotes": "",
"remove_alert": ""
"viewreleasenotes": ""
},
"errors": {
"fcm": "",
@@ -2758,9 +2757,7 @@
"total_lab_on_board": "",
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": "",
"tasks_in_production": "",
"tasks_on_board": ""
"total_jobs_on_board": ""
}
},
"actions": {
@@ -2795,7 +2792,6 @@
"model_info": "",
"actual_in": "",
"alert": "",
"tasks": "",
"alertoff": "",
"alerton": "",
"ats": "",
@@ -2849,9 +2845,6 @@
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": "",
"tasks_in_production": "",
"tasks_on_board": "",
"tasks": "",
"hours": "",
"currency_symbol": "",
"jobs": ""

View File

@@ -1160,8 +1160,7 @@
"submit": "",
"tryagain": "",
"view": "",
"viewreleasenotes": "",
"remove_alert": ""
"viewreleasenotes": ""
},
"errors": {
"fcm": "",
@@ -2758,9 +2757,7 @@
"total_lab_on_board": "",
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": "",
"tasks_in_production": "",
"tasks_on_board": ""
"total_jobs_on_board": ""
}
},
"actions": {
@@ -2795,7 +2792,6 @@
"model_info": "",
"actual_in": "",
"alert": "",
"tasks": "",
"alertoff": "",
"alerton": "",
"ats": "",
@@ -2849,9 +2845,6 @@
"total_lar_on_board": "",
"total_amount_on_board": "",
"total_jobs_on_board": "",
"tasks_in_production": "",
"tasks_on_board": "",
"tasks": "",
"hours": "",
"currency_symbol": "",
"jobs": ""

View File

@@ -1,9 +0,0 @@
-- 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
-- );

View File

@@ -1,7 +0,0 @@
UPDATE "public"."masterdata"
SET value = jsonb_set(
value::jsonb,
'{OP20}',
'{"desc": "REMOVE AND REINSTALL", "opcode": "OP20", "partcode": "PAE"}'::jsonb,
true
);

View File

@@ -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 && error.authResponse && error.authResponse.body) || (error && error.message),
error: (error && error.authResponse && error.authResponse.body) || (error && error.message),
method: "InsertBill"
});
throw error;

View File

@@ -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", "SHAW"];
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE"];
const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, {
imexshopid: kaizenShopsIDs