Compare commits
114 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8422ea83ae | ||
|
|
1b84087ef8 | ||
|
|
a9fdf3da18 | ||
|
|
fa2c729ac2 | ||
|
|
95bb5b03c2 | ||
|
|
318482c195 | ||
|
|
eea9e8e2cc | ||
|
|
cde12f9970 | ||
|
|
48def2b74d | ||
|
|
dde7a99956 | ||
|
|
49fb2caac0 | ||
|
|
df964aa14e | ||
|
|
7619360f37 | ||
|
|
f15f371e86 | ||
|
|
34fe0cc3bf | ||
|
|
7acaefb5c5 | ||
|
|
ab02da47a2 | ||
|
|
673670eeb4 | ||
|
|
2a7dec90d5 | ||
|
|
6e0b1f65a7 | ||
|
|
8671d1254d | ||
|
|
0ea254ed4e | ||
|
|
331dcfc063 | ||
|
|
c46804cfdf | ||
|
|
484d09d635 | ||
|
|
188a7b47b1 | ||
|
|
a6ca93f482 | ||
|
|
d08bfc61cd | ||
|
|
e6100851b8 | ||
|
|
e9795072d5 | ||
|
|
9b4de1645e | ||
|
|
503c217c99 | ||
|
|
2333067e02 | ||
|
|
953172493e | ||
|
|
b444639fca | ||
|
|
6ee7e56b9b | ||
|
|
ffd5acb21a | ||
|
|
0340ca5fcc | ||
|
|
1b2fc8b114 | ||
|
|
3745d7a414 | ||
|
|
a0efac9bd8 | ||
|
|
17a772563c | ||
|
|
b1ce356bd8 | ||
|
|
9818cac30e | ||
|
|
171277630e | ||
|
|
d8b400cb8c | ||
|
|
fe7bf684aa | ||
|
|
7e6c97b3cf | ||
|
|
773f3d4c84 | ||
|
|
9c6fe1905d | ||
|
|
2126cccff1 | ||
|
|
40d5e02415 | ||
|
|
5b891281d1 | ||
|
|
56559dd3ff | ||
|
|
fde137d7f7 | ||
|
|
b797bf7dc9 | ||
|
|
37c3be5cde | ||
|
|
b87d1a65fe | ||
|
|
35c832dbc3 | ||
|
|
019b3cf4da | ||
|
|
27f4385539 | ||
|
|
ad520ab23e | ||
|
|
b3716521ec | ||
|
|
05ae0801e5 | ||
|
|
332ade96e5 | ||
|
|
3acec55c0e | ||
|
|
da0462f14c | ||
|
|
2cc9fa961e | ||
|
|
2646e85863 | ||
|
|
1b6fe4d18e | ||
|
|
22aae0a7f1 | ||
|
|
71043313d6 | ||
|
|
c9620a3f6f | ||
|
|
cfbd6f93c3 | ||
|
|
cdfae5a429 | ||
|
|
db1b701a96 | ||
|
|
2746421c09 | ||
|
|
5217120994 | ||
|
|
77f72a2a12 | ||
|
|
a84ad4ee32 | ||
|
|
2cacd75822 | ||
|
|
217a0b84ac | ||
|
|
f53ed8c427 | ||
|
|
f8b7588a04 | ||
|
|
ee3cb4456d | ||
|
|
ae05692c46 | ||
|
|
e01a2af5a4 | ||
|
|
9c0cb5f80b | ||
|
|
1f726aca4d | ||
|
|
b9f398cf2d | ||
|
|
ff73a14610 | ||
|
|
1e44d4fe42 | ||
|
|
0f42875d1b | ||
|
|
a0f1299006 | ||
|
|
87d8a5d746 | ||
|
|
268851902a | ||
|
|
68bb7d2529 | ||
|
|
d50db12330 | ||
|
|
1438986c18 | ||
|
|
c047699fbb | ||
|
|
e5b7fcb919 | ||
|
|
cadcfc9b0d | ||
|
|
55023ceaca | ||
|
|
45e143578c | ||
|
|
28a41f7637 | ||
|
|
2a2edeadb9 | ||
|
|
849d967b56 | ||
|
|
519d7e8d87 | ||
|
|
6c814c7dc6 | ||
|
|
cc9e536059 | ||
|
|
b05e20ce0d | ||
|
|
eb36b12cb0 | ||
|
|
ff3d24c623 | ||
|
|
52c9b9a290 |
@@ -13,4 +13,5 @@
|
|||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
bodyshop_translations.babel
|
.env.localstack.docker
|
||||||
|
bodyshop_translations.babel
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
949
client/package-lock.json
generated
949
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,41 +8,45 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amplitude/analytics-browser": "^2.34.0",
|
"@amplitude/analytics-browser": "^2.35.0",
|
||||||
"@ant-design/pro-layout": "^7.22.6",
|
"@ant-design/pro-layout": "^7.22.6",
|
||||||
"@apollo/client": "^4.1.3",
|
"@apollo/client": "^4.1.4",
|
||||||
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
"@dnd-kit/modifiers": "^9.0.0",
|
||||||
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@emotion/is-prop-valid": "^1.4.0",
|
"@emotion/is-prop-valid": "^1.4.0",
|
||||||
"@fingerprintjs/fingerprintjs": "^5.0.1",
|
"@fingerprintjs/fingerprintjs": "^5.0.1",
|
||||||
"@firebase/analytics": "^0.10.19",
|
"@firebase/analytics": "^0.10.19",
|
||||||
"@firebase/app": "^0.14.7",
|
"@firebase/app": "^0.14.8",
|
||||||
"@firebase/auth": "^1.12.0",
|
"@firebase/auth": "^1.12.0",
|
||||||
"@firebase/firestore": "^4.10.0",
|
"@firebase/firestore": "^4.11.0",
|
||||||
"@firebase/messaging": "^0.12.22",
|
"@firebase/messaging": "^0.12.22",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.11.2",
|
"@reduxjs/toolkit": "^2.11.2",
|
||||||
"@sentry/cli": "^3.1.0",
|
"@sentry/cli": "^3.2.0",
|
||||||
"@sentry/react": "^10.38.0",
|
"@sentry/react": "^10.39.0",
|
||||||
"@sentry/vite-plugin": "^4.8.0",
|
"@sentry/vite-plugin": "^4.9.1",
|
||||||
"@splitsoftware/splitio-react": "^2.6.1",
|
"@splitsoftware/splitio-react": "^2.6.1",
|
||||||
"@tanem/react-nprogress": "^5.0.58",
|
"@tanem/react-nprogress": "^5.0.63",
|
||||||
"antd": "^6.2.2",
|
"antd": "^6.3.0",
|
||||||
"apollo-link-logger": "^3.0.0",
|
"apollo-link-logger": "^3.0.0",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.13.4",
|
"axios": "^1.13.5",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"css-box-model": "^1.2.1",
|
"css-box-model": "^1.2.1",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"dayjs-business-days2": "^1.3.2",
|
"dayjs-business-days2": "^1.3.2",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.3.1",
|
||||||
"env-cmd": "^11.0.0",
|
"env-cmd": "^11.0.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"graphql": "^16.12.0",
|
"graphql": "^16.12.0",
|
||||||
"graphql-ws": "^6.0.7",
|
"graphql-ws": "^6.0.7",
|
||||||
"i18next": "^25.8.0",
|
"i18next": "^25.8.11",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.1",
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"libphonenumber-js": "^1.12.36",
|
"libphonenumber-js": "^1.12.37",
|
||||||
"lightningcss": "^1.31.1",
|
"lightningcss": "^1.31.1",
|
||||||
"logrocket": "^12.0.0",
|
"logrocket": "^12.0.0",
|
||||||
"markerjs2": "^2.32.7",
|
"markerjs2": "^2.32.7",
|
||||||
@@ -50,7 +54,7 @@
|
|||||||
"normalize-url": "^8.1.1",
|
"normalize-url": "^8.1.1",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"phone": "^3.1.70",
|
"phone": "^3.1.70",
|
||||||
"posthog-js": "^1.336.4",
|
"posthog-js": "^1.351.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^9.3.1",
|
"query-string": "^9.3.1",
|
||||||
"raf-schd": "^4.0.3",
|
"raf-schd": "^4.0.3",
|
||||||
@@ -59,7 +63,6 @@
|
|||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^8.0.1",
|
"react-cookie": "^8.0.1",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"react-drag-listview": "^2.0.0",
|
|
||||||
"react-grid-gallery": "^1.0.1",
|
"react-grid-gallery": "^1.0.1",
|
||||||
"react-grid-layout": "^2.2.2",
|
"react-grid-layout": "^2.2.2",
|
||||||
"react-i18next": "^16.5.4",
|
"react-i18next": "^16.5.4",
|
||||||
@@ -84,7 +87,7 @@
|
|||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"sass": "^1.97.3",
|
"sass": "^1.97.3",
|
||||||
"socket.io-client": "^4.8.3",
|
"socket.io-client": "^4.8.3",
|
||||||
"styled-components": "^6.3.8",
|
"styled-components": "^6.3.10",
|
||||||
"vite-plugin-ejs": "^1.7.0",
|
"vite-plugin-ejs": "^1.7.0",
|
||||||
"web-vitals": "^5.1.0"
|
"web-vitals": "^5.1.0"
|
||||||
},
|
},
|
||||||
@@ -141,11 +144,11 @@
|
|||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@playwright/test": "^1.58.0",
|
"@playwright/test": "^1.58.2",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^5.1.4",
|
||||||
"babel-plugin-react-compiler": "^1.0.0",
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
"browserslist": "^4.28.1",
|
"browserslist": "^4.28.1",
|
||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
@@ -153,16 +156,16 @@
|
|||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
||||||
"globals": "^17.2.0",
|
"globals": "^17.3.0",
|
||||||
"jsdom": "^27.4.0",
|
"jsdom": "^28.1.0",
|
||||||
"memfs": "^4.56.10",
|
"memfs": "^4.56.10",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"playwright": "^1.58.0",
|
"playwright": "^1.58.2",
|
||||||
"react-error-overlay": "^6.1.0",
|
"react-error-overlay": "^6.1.0",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-plugin-babel": "^1.4.1",
|
"vite-plugin-babel": "^1.5.1",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-node-polyfills": "^0.25.0",
|
"vite-plugin-node-polyfills": "^0.25.0",
|
||||||
"vite-plugin-pwa": "^1.2.0",
|
"vite-plugin-pwa": "^1.2.0",
|
||||||
|
|||||||
@@ -446,3 +446,32 @@
|
|||||||
//.rbc-time-header-gutter {
|
//.rbc-time-header-gutter {
|
||||||
// padding: 0;
|
// padding: 0;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
/* globally allow shrink inside table cells */
|
||||||
|
.prod-list-table .ant-table-cell,
|
||||||
|
.prod-list-table .ant-table-cell > * {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* common AntD offenders */
|
||||||
|
.prod-list-table > .ant-table-cell .ant-space,
|
||||||
|
.ant-table-cell .ant-space-item {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep your custom header content on the left, push AntD sorter to the far right */
|
||||||
|
.prod-list-table .ant-table-column-sorters {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prod-list-table .ant-table-column-title {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0; /* allows ellipsis to work */
|
||||||
|
}
|
||||||
|
|
||||||
|
.prod-list-table .ant-table-column-sorter {
|
||||||
|
margin-left: auto;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ placement: "top", pageSize: exportPageLimit }}
|
pagination={{ placement: "top", pageSize: exportPageLimit, showSizeChanger: false }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ placement: "top", pageSize: exportPageLimit }}
|
pagination={{ placement: "top", pageSize: exportPageLimit, showSizeChanger: false }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ placement: "top", pageSize: exportPageLimit }}
|
pagination={{ placement: "top", pageSize: exportPageLimit, showSizeChanger: false }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -29,19 +29,18 @@ export function AllocationsAssignmentComponent({
|
|||||||
<Select
|
<Select
|
||||||
id="employeeSelector"
|
id="employeeSelector"
|
||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "children",
|
optionFilterProp: "label",
|
||||||
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
filterOption: (input, option) => option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
placeholder="Select a person"
|
placeholder="Select a person"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
>
|
options={bodyshop.employees.map((emp) => ({
|
||||||
{bodyshop.employees.map((emp) => (
|
value: emp.id,
|
||||||
<Select.Option value={emp.id} key={emp.id}>
|
key: emp.id,
|
||||||
{`${emp.first_name} ${emp.last_name}`}
|
label: `${emp.first_name} ${emp.last_name}`
|
||||||
</Select.Option>
|
}))}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
<InputNumber
|
<InputNumber
|
||||||
defaultValue={assignment.hours}
|
defaultValue={assignment.hours}
|
||||||
placeholder={t("joblines.fields.mod_lb_hrs")}
|
placeholder={t("joblines.fields.mod_lb_hrs")}
|
||||||
|
|||||||
@@ -31,19 +31,17 @@ export default connect(
|
|||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "children",
|
optionFilterProp: "label",
|
||||||
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
filterOption: (input, option) => option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
placeholder="Select a person"
|
placeholder="Select a person"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
>
|
options={bodyshop.employees.map((emp) => ({
|
||||||
{bodyshop.employees.map((emp) => (
|
value: emp.id,
|
||||||
<Select.Option value={emp.id} key={emp.id}>
|
label: `${emp.first_name} ${emp.last_name}`
|
||||||
{`${emp.first_name} ${emp.last_name}`}
|
}))}
|
||||||
</Select.Option>
|
/>
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<Button type="primary" disabled={!assignment.employeeid} onClick={handleAssignment}>
|
<Button type="primary" disabled={!assignment.employeeid} onClick={handleAssignment}>
|
||||||
Assign
|
Assign
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ export default function BillCmdReturnsTableComponent({ form, returnLoading, retu
|
|||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<tr key={field.key}>
|
<tr key={field.key}>
|
||||||
<td>
|
<td>
|
||||||
|
<Form.Item hidden key={`${index}id`} name={[field.name, "id"]}>
|
||||||
|
<ReadOnlyFormItemComponent />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
// label={t("joblines.fields.line_desc")}
|
// label={t("joblines.fields.line_desc")}
|
||||||
key={`${index}line_desc`}
|
key={`${index}line_desc`}
|
||||||
|
|||||||
@@ -99,20 +99,22 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
}}
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("billlines.fields.cost_center")} name={["billlineskeys", record.id, "cost_center"]}>
|
<Form.Item label={t("billlines.fields.cost_center")} name={["billlineskeys", record.id, "cost_center"]}>
|
||||||
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
<Select
|
||||||
{bodyshopHasDmsKey(bodyshop)
|
showSearch
|
||||||
? CiecaSelect(true, false)
|
style={{ minWidth: "3rem" }}
|
||||||
: responsibilityCenters.costs.map((item) => <Select.Option key={item.name}>{item.name}</Select.Option>)}
|
disabled={disabled}
|
||||||
</Select>
|
options={
|
||||||
|
bodyshopHasDmsKey(bodyshop)
|
||||||
|
? CiecaSelect(true, false)
|
||||||
|
: responsibilityCenters.costs.map((item) => ({ value: item.name, label: item.name }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("billlines.fields.location")} name={["billlineskeys", record.id, "location"]}>
|
<Form.Item label={t("billlines.fields.location")} name={["billlineskeys", record.id, "location"]}>
|
||||||
<Select disabled={disabled}>
|
<Select
|
||||||
{bodyshop.md_parts_locations.map((loc, idx) => (
|
disabled={disabled}
|
||||||
<Select.Option key={idx} value={loc}>
|
options={bodyshop.md_parts_locations.map((loc) => ({ value: loc, label: loc }))}
|
||||||
{loc}
|
/>
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("billlines.fields.deductedfromlbr")}
|
label={t("billlines.fields.deductedfromlbr")}
|
||||||
@@ -136,22 +138,10 @@ export function BillFormItemsExtendedFormItem({
|
|||||||
]}
|
]}
|
||||||
name={["billlineskeys", record.id, "lbr_adjustment", "mod_lbr_ty"]}
|
name={["billlineskeys", record.id, "lbr_adjustment", "mod_lbr_ty"]}
|
||||||
>
|
>
|
||||||
<Select allowClear>
|
<Select
|
||||||
<Select.Option value="LAA">{t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
allowClear
|
||||||
<Select.Option value="LAB">{t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
options={CiecaSelect(false, true)}
|
||||||
<Select.Option value="LAD">{t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
/>
|
||||||
<Select.Option value="LAE">{t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
|
||||||
<Select.Option value="LAF">{t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
|
||||||
<Select.Option value="LAG">{t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
|
||||||
<Select.Option value="LAM">{t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
|
||||||
<Select.Option value="LAR">{t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
|
||||||
<Select.Option value="LAS">{t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
|
||||||
<Select.Option value="LAU">{t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
|
||||||
<Select.Option value="LA1">{t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
|
||||||
<Select.Option value="LA2">{t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
|
||||||
<Select.Option value="LA3">{t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
|
||||||
<Select.Option value="LA4">{t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("jobs.labels.adjustmentrate")}
|
label={t("jobs.labels.adjustmentrate")}
|
||||||
|
|||||||
@@ -328,13 +328,12 @@ export function BillFormComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
{!billEdit && (
|
{!billEdit && (
|
||||||
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
|
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
|
||||||
<Select style={{ width: "10rem" }} disabled={disabled} allowClear>
|
<Select
|
||||||
{bodyshop.md_parts_locations.map((loc, idx) => (
|
style={{ width: "10rem" }}
|
||||||
<Select.Option key={idx} value={loc}>
|
disabled={disabled}
|
||||||
{loc}
|
allowClear
|
||||||
</Select.Option>
|
options={bodyshop.md_parts_locations.map((loc) => ({ value: loc, label: loc }))}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
@@ -373,9 +372,11 @@ export function BillFormComponent({
|
|||||||
"local_tax_rate"
|
"local_tax_rate"
|
||||||
]);
|
]);
|
||||||
let totals;
|
let totals;
|
||||||
if (!!values.total && !!values.billlines && values.billlines.length > 0)
|
if (!!values.total && !!values.billlines && values.billlines.length > 0) {
|
||||||
totals = CalculateBillTotal(values);
|
totals = CalculateBillTotal(values);
|
||||||
if (totals)
|
}
|
||||||
|
|
||||||
|
if (totals) {
|
||||||
return (
|
return (
|
||||||
// TODO: Align is not correct
|
// TODO: Align is not correct
|
||||||
// eslint-disable-next-line react/no-unknown-property
|
// eslint-disable-next-line react/no-unknown-property
|
||||||
@@ -414,7 +415,7 @@ export function BillFormComponent({
|
|||||||
<Statistic
|
<Statistic
|
||||||
title={t("bills.labels.discrepancy")}
|
title={t("bills.labels.discrepancy")}
|
||||||
styles={{
|
styles={{
|
||||||
value: {
|
content: {
|
||||||
color: totals.discrepancy.getAmount() === 0 ? "green" : "red"
|
color: totals.discrepancy.getAmount() === 0 ? "green" : "red"
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -427,6 +428,7 @@ export function BillFormComponent({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
||||||
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip } from "antd";
|
import { Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip } from "antd";
|
||||||
|
import { useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -32,6 +33,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
||||||
|
const firstFieldRefs = useRef({});
|
||||||
|
|
||||||
const CONTROL_HEIGHT = 32;
|
const CONTROL_HEIGHT = 32;
|
||||||
|
|
||||||
@@ -90,6 +92,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only fill actual_cost when the user forward-tabs out of Retail (actual_price)
|
||||||
const autofillActualCost = (index) => {
|
const autofillActualCost = (index) => {
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
const retailRaw = form.getFieldValue(["billlines", index, "actual_price"]);
|
const retailRaw = form.getFieldValue(["billlines", index, "actual_price"]);
|
||||||
@@ -154,6 +157,9 @@ export function BillEnterModalLinesComponent({
|
|||||||
),
|
),
|
||||||
formInput: (record, index) => (
|
formInput: (record, index) => (
|
||||||
<BillLineSearchSelect
|
<BillLineSearchSelect
|
||||||
|
ref={(el) => {
|
||||||
|
firstFieldRefs.current[index] = el;
|
||||||
|
}}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
options={lineData}
|
options={lineData}
|
||||||
style={{
|
style={{
|
||||||
@@ -164,10 +170,9 @@ export function BillEnterModalLinesComponent({
|
|||||||
}}
|
}}
|
||||||
allowRemoved={form.getFieldValue("is_credit_memo") || false}
|
allowRemoved={form.getFieldValue("is_credit_memo") || false}
|
||||||
onSelect={(value, opt) => {
|
onSelect={(value, opt) => {
|
||||||
const d = normalizeDiscount(discount);
|
// IMPORTANT:
|
||||||
const retail = Number(opt.cost);
|
// Do NOT autofill actual_cost here. It should only fill when the user forward-tabs
|
||||||
const computedActual = Number.isFinite(retail) ? round2(retail * (1 - d)) : null;
|
// from Retail (actual_price) -> Actual Cost (actual_cost).
|
||||||
|
|
||||||
setFieldsValue({
|
setFieldsValue({
|
||||||
billlines: (getFieldValue("billlines") || []).map((item, idx) => {
|
billlines: (getFieldValue("billlines") || []).map((item, idx) => {
|
||||||
if (idx !== index) return item;
|
if (idx !== index) return item;
|
||||||
@@ -178,7 +183,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
quantity: opt.part_qty || 1,
|
quantity: opt.part_qty || 1,
|
||||||
actual_price: opt.cost,
|
actual_price: opt.cost,
|
||||||
original_actual_price: opt.cost,
|
original_actual_price: opt.cost,
|
||||||
actual_cost: isBlank(item.actual_cost) ? computedActual : item.actual_cost,
|
// actual_cost intentionally untouched here
|
||||||
cost_center: opt.part_type
|
cost_center: opt.part_type
|
||||||
? bodyshopHasDmsKey(bodyshop)
|
? bodyshopHasDmsKey(bodyshop)
|
||||||
? opt.part_type !== "PAE"
|
? opt.part_type !== "PAE"
|
||||||
@@ -205,7 +210,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
label: t("billlines.fields.line_desc"),
|
label: t("billlines.fields.line_desc"),
|
||||||
rules: [{ required: true }]
|
rules: [{ required: true }]
|
||||||
}),
|
}),
|
||||||
formInput: () => <Input.TextArea disabled={disabled} autoSize />
|
formInput: () => <Input.TextArea disabled={disabled} autoSize tabIndex={0} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.quantity"),
|
title: t("billlines.fields.quantity"),
|
||||||
@@ -234,7 +239,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
})
|
})
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
formInput: () => <InputNumber precision={0} min={1} disabled={disabled} />
|
formInput: () => <InputNumber precision={0} min={1} disabled={disabled} tabIndex={0} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.actual_price"),
|
title: t("billlines.fields.actual_price"),
|
||||||
@@ -251,9 +256,10 @@ export function BillEnterModalLinesComponent({
|
|||||||
<CurrencyInput
|
<CurrencyInput
|
||||||
min={0}
|
min={0}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onBlur={() => autofillActualCost(index)}
|
tabIndex={0}
|
||||||
|
// NOTE: Autofill should only happen on forward Tab out of Retail
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Tab") autofillActualCost(index);
|
if (e.key === "Tab" && !e.shiftKey) autofillActualCost(index);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -328,6 +334,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
min={0}
|
min={0}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
controls={false}
|
controls={false}
|
||||||
|
tabIndex={0}
|
||||||
style={{ width: "100%", height: CONTROL_HEIGHT }}
|
style={{ width: "100%", height: CONTROL_HEIGHT }}
|
||||||
onFocus={() => autofillActualCost(index)}
|
onFocus={() => autofillActualCost(index)}
|
||||||
/>
|
/>
|
||||||
@@ -392,11 +399,17 @@ export function BillEnterModalLinesComponent({
|
|||||||
rules: [{ required: true }]
|
rules: [{ required: true }]
|
||||||
}),
|
}),
|
||||||
formInput: () => (
|
formInput: () => (
|
||||||
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
<Select
|
||||||
{bodyshopHasDmsKey(bodyshop)
|
showSearch
|
||||||
? CiecaSelect(true, false)
|
style={{ minWidth: "3rem" }}
|
||||||
: responsibilityCenters.costs.map((item) => <Select.Option key={item.name}>{item.name}</Select.Option>)}
|
disabled={disabled}
|
||||||
</Select>
|
tabIndex={0}
|
||||||
|
options={
|
||||||
|
bodyshopHasDmsKey(bodyshop)
|
||||||
|
? CiecaSelect(true, false)
|
||||||
|
: responsibilityCenters.costs.map((item) => ({ value: item.name, label: item.name }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
...(billEdit
|
...(billEdit
|
||||||
@@ -412,13 +425,11 @@ export function BillEnterModalLinesComponent({
|
|||||||
name: [field.name, "location"]
|
name: [field.name, "location"]
|
||||||
}),
|
}),
|
||||||
formInput: () => (
|
formInput: () => (
|
||||||
<Select disabled={disabled}>
|
<Select
|
||||||
{bodyshop.md_parts_locations.map((loc, idx) => (
|
disabled={disabled}
|
||||||
<Select.Option key={idx} value={loc}>
|
tabIndex={0}
|
||||||
{loc}
|
options={bodyshop.md_parts_locations.map((loc) => ({ value: loc, label: loc }))}
|
||||||
</Select.Option>
|
/>
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
]),
|
]),
|
||||||
@@ -432,7 +443,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
key: `${field.name}deductedfromlbr`,
|
key: `${field.name}deductedfromlbr`,
|
||||||
name: [field.name, "deductedfromlbr"]
|
name: [field.name, "deductedfromlbr"]
|
||||||
}),
|
}),
|
||||||
formInput: () => <Switch disabled={disabled} />,
|
formInput: () => <Switch disabled={disabled} tabIndex={0} />,
|
||||||
additional: (record, index) => (
|
additional: (record, index) => (
|
||||||
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
||||||
{() => {
|
{() => {
|
||||||
@@ -459,22 +470,10 @@ export function BillEnterModalLinesComponent({
|
|||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
name={[record.name, "lbr_adjustment", "mod_lbr_ty"]}
|
name={[record.name, "lbr_adjustment", "mod_lbr_ty"]}
|
||||||
>
|
>
|
||||||
<Select allowClear>
|
<Select
|
||||||
<Select.Option value="LAA">{t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
allowClear
|
||||||
<Select.Option value="LAB">{t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
options={CiecaSelect(false, true)}
|
||||||
<Select.Option value="LAD">{t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
/>
|
||||||
<Select.Option value="LAE">{t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
|
||||||
<Select.Option value="LAF">{t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
|
||||||
<Select.Option value="LAG">{t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
|
||||||
<Select.Option value="LAM">{t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
|
||||||
<Select.Option value="LAR">{t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
|
||||||
<Select.Option value="LAS">{t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
|
||||||
<Select.Option value="LAU">{t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
|
||||||
<Select.Option value="LA1">{t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
|
||||||
<Select.Option value="LA2">{t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
|
||||||
<Select.Option value="LA3">{t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
|
||||||
<Select.Option value="LA4">{t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{Enhanced_Payroll.treatment === "on" ? (
|
{Enhanced_Payroll.treatment === "on" ? (
|
||||||
@@ -517,9 +516,13 @@ export function BillEnterModalLinesComponent({
|
|||||||
formItemProps: (field) => ({
|
formItemProps: (field) => ({
|
||||||
key: `${field.name}fedtax`,
|
key: `${field.name}fedtax`,
|
||||||
valuePropName: "checked",
|
valuePropName: "checked",
|
||||||
name: [field.name, "applicable_taxes", "federal"]
|
name: [field.name, "applicable_taxes", "federal"],
|
||||||
|
initialValue: InstanceRenderManager({
|
||||||
|
imex: true,
|
||||||
|
rome: false
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
formInput: () => <Switch disabled={disabled} />
|
formInput: () => <Switch disabled={disabled} tabIndex={0} />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
@@ -534,7 +537,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
valuePropName: "checked",
|
valuePropName: "checked",
|
||||||
name: [field.name, "applicable_taxes", "state"]
|
name: [field.name, "applicable_taxes", "state"]
|
||||||
}),
|
}),
|
||||||
formInput: () => <Switch disabled={disabled} />
|
formInput: () => <Switch disabled={disabled} tabIndex={0} />
|
||||||
},
|
},
|
||||||
|
|
||||||
...InstanceRenderManager({
|
...InstanceRenderManager({
|
||||||
@@ -550,7 +553,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
valuePropName: "checked",
|
valuePropName: "checked",
|
||||||
name: [field.name, "applicable_taxes", "local"]
|
name: [field.name, "applicable_taxes", "local"]
|
||||||
}),
|
}),
|
||||||
formInput: () => <Switch disabled={disabled} />
|
formInput: () => <Switch disabled={disabled} tabIndex={0} />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
@@ -570,6 +573,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
icon={<DeleteFilled />}
|
icon={<DeleteFilled />}
|
||||||
disabled={disabled || invLen > 0}
|
disabled={disabled || invLen > 0}
|
||||||
onClick={() => remove(record.name)}
|
onClick={() => remove(record.name)}
|
||||||
|
tabIndex={0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{Simple_Inventory.treatment === "on" && (
|
{Simple_Inventory.treatment === "on" && (
|
||||||
@@ -641,12 +645,19 @@ export function BillEnterModalLinesComponent({
|
|||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
const newIndex = fields.length;
|
||||||
add(
|
add(
|
||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
imex: { applicable_taxes: { federal: true } },
|
imex: { applicable_taxes: { federal: true } },
|
||||||
rome: { applicable_taxes: { federal: false } }
|
rome: { applicable_taxes: { federal: false } }
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
const firstField = firstFieldRefs.current[newIndex];
|
||||||
|
if (firstField?.focus) {
|
||||||
|
firstField.focus();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
}}
|
}}
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ const CardPaymentModalComponent = ({
|
|||||||
QUERY_RO_AND_OWNER_BY_JOB_PKS,
|
QUERY_RO_AND_OWNER_BY_JOB_PKS,
|
||||||
{
|
{
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
notifyOnNetworkStatusChange: true
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
|
|||||||
const [getConversations, { loading, data, refetch, called }] = useLazyQuery(CONVERSATION_LIST_QUERY, {
|
const [getConversations, { loading, data, refetch, called }] = useLazyQuery(CONVERSATION_LIST_QUERY, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
notifyOnNetworkStatusChange: true,
|
|
||||||
...(pollInterval > 0 ? { pollInterval } : {})
|
...(pollInterval > 0 ? { pollInterval } : {})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,12 @@ export default function ChatTagRoComponent({ roOptions, loading, handleSearch, h
|
|||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
onSelect={handleInsertTag}
|
onSelect={handleInsertTag}
|
||||||
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
||||||
>
|
options={roOptions.map((item, idx) => ({
|
||||||
{roOptions.map((item, idx) => (
|
key: item.id || idx,
|
||||||
<Select.Option key={item.id || idx}>
|
value: item.id || idx,
|
||||||
{` ${item.ro_number || ""} | ${OwnerNameDisplayFunction(item)}`}
|
label: ` ${item.ro_number || ""} | ${OwnerNameDisplayFunction(item)}`
|
||||||
</Select.Option>
|
}))}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
{loading ? <LoadingOutlined /> : null}
|
{loading ? <LoadingOutlined /> : null}
|
||||||
|
|
||||||
|
|||||||
@@ -309,13 +309,13 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select
|
||||||
{bodyshop.md_ins_cos.map((s) => (
|
options={bodyshop.md_ins_cos.map((s) => ({
|
||||||
<Select.Option key={s.name} value={s.name}>
|
key: s.name,
|
||||||
{s.name}
|
value: s.name,
|
||||||
</Select.Option>
|
label: s.name
|
||||||
))}
|
}))}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={"class"}
|
name={"class"}
|
||||||
@@ -327,13 +327,13 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select
|
||||||
{bodyshop.md_classes.map((s) => (
|
options={bodyshop.md_classes.map((s) => ({
|
||||||
<Select.Option key={s} value={s}>
|
key: s,
|
||||||
{s}
|
value: s,
|
||||||
</Select.Option>
|
label: s
|
||||||
))}
|
}))}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("contracts.labels.convertform.applycleanupcharge")}
|
label={t("contracts.labels.convertform.applycleanupcharge")}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { useEffect, useState } from "react";
|
|||||||
import { Select } from "antd";
|
import { Select } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const ContractStatusComponent = ({ value, onChange, ref }) => {
|
const ContractStatusComponent = ({ value, onChange, ref }) => {
|
||||||
const [option, setOption] = useState(value);
|
const [option, setOption] = useState(value);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -15,11 +13,17 @@ const ContractStatusComponent = ({ value, onChange, ref }) => {
|
|||||||
}, [value, option, onChange]);
|
}, [value, option, onChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select ref={ref} value={option} style={{ width: 100 }} onChange={setOption}>
|
<Select
|
||||||
<Option value="contracts.status.new">{t("contracts.status.new")}</Option>
|
ref={ref}
|
||||||
<Option value="contracts.status.out">{t("contracts.status.out")}</Option>
|
value={option}
|
||||||
<Option value="contracts.status.returned">{t("contracts.status.out")}</Option>
|
style={{ width: 100 }}
|
||||||
</Select>
|
onChange={setOption}
|
||||||
|
options={[
|
||||||
|
{ value: "contracts.status.new", label: t("contracts.status.new") },
|
||||||
|
{ value: "contracts.status.out", label: t("contracts.status.out") },
|
||||||
|
{ value: "contracts.status.returned", label: t("contracts.status.out") }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { Select } from "antd";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const CourtesyCarReadinessComponent = ({ value, onChange, ref }) => {
|
const CourtesyCarReadinessComponent = ({ value, onChange, ref }) => {
|
||||||
const [option, setOption] = useState(value);
|
const [option, setOption] = useState(value);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -23,10 +21,11 @@ const CourtesyCarReadinessComponent = ({ value, onChange, ref }) => {
|
|||||||
width: 100
|
width: 100
|
||||||
}}
|
}}
|
||||||
onChange={setOption}
|
onChange={setOption}
|
||||||
>
|
options={[
|
||||||
<Option value="courtesycars.readiness.ready">{t("courtesycars.readiness.ready")}</Option>
|
{ value: "courtesycars.readiness.ready", label: t("courtesycars.readiness.ready") },
|
||||||
<Option value="courtesycars.readiness.notready">{t("courtesycars.readiness.notready")}</Option>
|
{ value: "courtesycars.readiness.notready", label: t("courtesycars.readiness.notready") }
|
||||||
</Select>
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CourtesyCarReadinessComponent;
|
export default CourtesyCarReadinessComponent;
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { useEffect, useState } from "react";
|
|||||||
import { Select } from "antd";
|
import { Select } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const CourtesyCarStatusComponent = ({ value, onChange, ref }) => {
|
const CourtesyCarStatusComponent = ({ value, onChange, ref }) => {
|
||||||
const [option, setOption] = useState(value);
|
const [option, setOption] = useState(value);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -22,14 +20,15 @@ const CourtesyCarStatusComponent = ({ value, onChange, ref }) => {
|
|||||||
width: 100
|
width: 100
|
||||||
}}
|
}}
|
||||||
onChange={setOption}
|
onChange={setOption}
|
||||||
>
|
options={[
|
||||||
<Option value="courtesycars.status.in">{t("courtesycars.status.in")}</Option>
|
{ value: "courtesycars.status.in", label: t("courtesycars.status.in") },
|
||||||
<Option value="courtesycars.status.inservice">{t("courtesycars.status.inservice")}</Option>
|
{ value: "courtesycars.status.inservice", label: t("courtesycars.status.inservice") },
|
||||||
<Option value="courtesycars.status.out">{t("courtesycars.status.out")}</Option>
|
{ value: "courtesycars.status.out", label: t("courtesycars.status.out") },
|
||||||
<Option value="courtesycars.status.sold">{t("courtesycars.status.sold")}</Option>
|
{ value: "courtesycars.status.sold", label: t("courtesycars.status.sold") },
|
||||||
<Option value="courtesycars.status.leasereturn">{t("courtesycars.status.leasereturn")}</Option>
|
{ value: "courtesycars.status.leasereturn", label: t("courtesycars.status.leasereturn") },
|
||||||
<Option value="courtesycars.status.unavailable">{t("courtesycars.status.unavailable")}</Option>
|
{ value: "courtesycars.status.unavailable", label: t("courtesycars.status.unavailable") }
|
||||||
</Select>
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CourtesyCarStatusComponent;
|
export default CourtesyCarStatusComponent;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function DashboardTotalProductionHours({ bodyshop, data, ...cardProps })
|
|||||||
<Statistic
|
<Statistic
|
||||||
title={t("dashboard.labels.prodhrs")}
|
title={t("dashboard.labels.prodhrs")}
|
||||||
value={hours.total.toFixed(1)}
|
value={hours.total.toFixed(1)}
|
||||||
styles={{ value: { color: aboveTargetHours ? "green" : "red" } }}
|
styles={{ content: { color: aboveTargetHours ? "green" : "red" } }}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector)
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export function DmsCustomerSelector(props) {
|
export function DmsCustomerSelector(props) {
|
||||||
const { bodyshop, jobid, socket, rrOptions = {} } = props;
|
const { bodyshop, jobid, job, socket, rrOptions = {} } = props;
|
||||||
|
|
||||||
// Centralized "mode" (provider + transport)
|
// Centralized "mode" (provider + transport)
|
||||||
const mode = props.mode;
|
const mode = props.mode;
|
||||||
|
|
||||||
// Stable base props for children
|
// Stable base props for children
|
||||||
const base = useMemo(() => ({ bodyshop, jobid, socket }), [bodyshop, jobid, socket]);
|
const base = useMemo(() => ({ bodyshop, jobid, job, socket }), [bodyshop, jobid, job, socket]);
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case DMS_MAP.reynolds: {
|
case DMS_MAP.reynolds: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Alert, Button, Checkbox, Col, message, Space, Table } from "antd";
|
import { Alert, Button, Checkbox, message, Modal, Space, Table } from "antd";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
@@ -47,6 +47,7 @@ const rrAddressToString = (addr) => {
|
|||||||
export default function RRCustomerSelector({
|
export default function RRCustomerSelector({
|
||||||
jobid,
|
jobid,
|
||||||
socket,
|
socket,
|
||||||
|
job,
|
||||||
rrOpenRoLimit = false,
|
rrOpenRoLimit = false,
|
||||||
onRrOpenRoFinished,
|
onRrOpenRoFinished,
|
||||||
rrValidationPending = false,
|
rrValidationPending = false,
|
||||||
@@ -59,15 +60,26 @@ export default function RRCustomerSelector({
|
|||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
// Show dialog automatically when validation is pending
|
// Show dialog automatically when validation is pending
|
||||||
|
// BUT: skip this for early RO flow (job already has dms_id)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (rrValidationPending) setOpen(true);
|
if (rrValidationPending && !job?.dms_id) {
|
||||||
}, [rrValidationPending]);
|
setOpen(true);
|
||||||
|
}
|
||||||
|
}, [rrValidationPending, job?.dms_id]);
|
||||||
|
|
||||||
// Listen for RR customer selection list
|
// Listen for RR customer selection list
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
const handleRrSelectCustomer = (list) => {
|
const handleRrSelectCustomer = (list) => {
|
||||||
const normalized = normalizeRrList(list);
|
const normalized = normalizeRrList(list);
|
||||||
|
|
||||||
|
// If list is empty, it means early RO exists and customer selection should be skipped
|
||||||
|
// Don't open the modal in this case
|
||||||
|
if (normalized.length === 0) {
|
||||||
|
setRefreshing(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setCustomerList(normalized);
|
setCustomerList(normalized);
|
||||||
const firstOwner = normalized.find((r) => r.vinOwner)?.custNo;
|
const firstOwner = normalized.find((r) => r.vinOwner)?.custNo;
|
||||||
@@ -127,6 +139,10 @@ export default function RRCustomerSelector({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
const refreshRrSearch = () => {
|
const refreshRrSearch = () => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
const to = setTimeout(() => setRefreshing(false), 12000);
|
const to = setTimeout(() => setRefreshing(false), 12000);
|
||||||
@@ -141,8 +157,6 @@ export default function RRCustomerSelector({
|
|||||||
socket.emit("rr-export-job", { jobId: jobid });
|
socket.emit("rr-export-job", { jobId: jobid });
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!open) return null;
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ title: t("jobs.fields.dms.id"), dataIndex: "custNo", key: "custNo" },
|
{ title: t("jobs.fields.dms.id"), dataIndex: "custNo", key: "custNo" },
|
||||||
{
|
{
|
||||||
@@ -169,8 +183,45 @@ export default function RRCustomerSelector({
|
|||||||
return !rrOwnerSet.has(String(record.custNo));
|
return !rrOwnerSet.has(String(record.custNo));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// For early RO flow: show validation banner even when modal is closed
|
||||||
|
if (!open) {
|
||||||
|
if (rrValidationPending && job?.dms_id) {
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Alert
|
||||||
|
type="info"
|
||||||
|
showIcon
|
||||||
|
title="Complete Validation in Reynolds"
|
||||||
|
description={
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||||
|
<div>
|
||||||
|
We created the Repair Order. Please validate the totals and taxes in the DMS system. When done,
|
||||||
|
click <strong>Finished</strong> to finalize and mark this export as complete.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" onClick={onValidationFinished}>
|
||||||
|
Finished
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col span={24}>
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onCancel={handleClose}
|
||||||
|
footer={null}
|
||||||
|
width={800}
|
||||||
|
title={t("dms.selectCustomer")}
|
||||||
|
>
|
||||||
<Table
|
<Table
|
||||||
title={() => (
|
title={() => (
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||||
@@ -196,8 +247,8 @@ export default function RRCustomerSelector({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Validation step banner */}
|
{/* Validation step banner - only show for NON-early RO flow (legacy) */}
|
||||||
{rrValidationPending && (
|
{rrValidationPending && !job?.dms_id && (
|
||||||
<Alert
|
<Alert
|
||||||
type="info"
|
type="info"
|
||||||
showIcon
|
showIcon
|
||||||
@@ -262,6 +313,6 @@ export default function RRCustomerSelector({
|
|||||||
getCheckboxProps: (record) => ({ disabled: rrDisableRow(record) })
|
getCheckboxProps: (record) => ({ disabled: rrDisableRow(record) })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function DmsLogEvents({
|
|||||||
return {
|
return {
|
||||||
key: idx,
|
key: idx,
|
||||||
color: logLevelColor(level),
|
color: logLevelColor(level),
|
||||||
children: (
|
content: (
|
||||||
<Space orientation="vertical" size={4} style={{ display: "flex" }}>
|
<Space orientation="vertical" size={4} style={{ display: "flex" }}>
|
||||||
{/* Row 1: summary + inline "Details" toggle */}
|
{/* Row 1: summary + inline "Details" toggle */}
|
||||||
<Space wrap align="start">
|
<Space wrap align="start">
|
||||||
@@ -113,7 +113,7 @@ export function DmsLogEvents({
|
|||||||
[logs, openSet, colorizeJson, isDarkMode, showDetails]
|
[logs, openSet, colorizeJson, isDarkMode, showDetails]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Timeline pending reverse items={items} />;
|
return <Timeline reverse items={items} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -272,11 +272,19 @@ export default function CdkLikePostForm({ bodyshop, socket, job, logsRef, mode,
|
|||||||
name={[field.name, "name"]}
|
name={[field.name, "name"]}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Select style={{ width: "100%" }} onSelect={(value) => handlePayerSelect(value, index)}>
|
<Select
|
||||||
{bodyshop.cdk_configuration?.payers?.map((payer) => (
|
showSearch={{
|
||||||
<Select.Option key={payer.name}>{payer.name}</Select.Option>
|
optionFilterProp: "label",
|
||||||
))}
|
filterOption: (input, option) => option.label.toLowerCase().includes(input.toLowerCase())
|
||||||
</Select>
|
}}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onSelect={(value) => handlePayerSelect(value, index)}
|
||||||
|
options={bodyshop.cdk_configuration?.payers?.map((payer) => ({
|
||||||
|
key: payer.name,
|
||||||
|
value: payer.name,
|
||||||
|
label: payer.name
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
@@ -404,7 +412,7 @@ export default function CdkLikePostForm({ bodyshop, socket, job, logsRef, mode,
|
|||||||
<Typography.Title>=</Typography.Title>
|
<Typography.Title>=</Typography.Title>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("jobs.labels.dms.notallocated")}
|
title={t("jobs.labels.dms.notallocated")}
|
||||||
styles={{ value: { color: discrep.getAmount() === 0 ? "green" : "red" } }}
|
styles={{ content: { color: discrep.getAmount() === 0 ? "green" : "red" } }}
|
||||||
value={discrep.toFormat()}
|
value={discrep.toFormat()}
|
||||||
/>
|
/>
|
||||||
<Button disabled={disablePost} htmlType="submit">
|
<Button disabled={disablePost} htmlType="submit">
|
||||||
|
|||||||
@@ -208,8 +208,18 @@ export default function RRPostForm({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if early RO was created (job has all early RO fields)
|
||||||
|
const hasEarlyRO = !!(job?.dms_id && job?.dms_customer_id && job?.dms_advisor_id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={t("jobs.labels.dms.postingform")}>
|
<Card title={t("jobs.labels.dms.postingform")}>
|
||||||
|
{hasEarlyRO && (
|
||||||
|
<Typography.Paragraph type="success" strong style={{ marginBottom: 16 }}>
|
||||||
|
✅ {t("jobs.labels.dms.earlyro.created")} {job.dms_id}
|
||||||
|
<br />
|
||||||
|
<Typography.Text type="secondary">{t("jobs.labels.dms.earlyro.willupdate")}</Typography.Text>
|
||||||
|
</Typography.Paragraph>
|
||||||
|
)}
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
@@ -218,96 +228,96 @@ export default function RRPostForm({
|
|||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
>
|
>
|
||||||
<Row gutter={[16, 12]} align="bottom">
|
<Row gutter={[16, 12]} align="bottom">
|
||||||
{/* Advisor + inline Refresh */}
|
{/* Advisor + inline Refresh - Only show if no early RO */}
|
||||||
<Col xs={24} sm={24} md={12} lg={8}>
|
{!hasEarlyRO && (
|
||||||
<Form.Item label={t("jobs.fields.dms.advisor")} required>
|
<Col xs={24} sm={24} md={12} lg={8}>
|
||||||
<Space.Compact block>
|
<Form.Item label={t("jobs.fields.dms.advisor")} required>
|
||||||
<Form.Item
|
<Space.Compact block>
|
||||||
name="advisorNo"
|
<Form.Item
|
||||||
noStyle
|
name="advisorNo"
|
||||||
rules={[{ required: true, message: t("general.validation.required") }]}
|
noStyle
|
||||||
>
|
rules={[{ required: true, message: t("general.validation.required") }]}
|
||||||
<Select
|
>
|
||||||
style={{ flex: 1 }}
|
<Select
|
||||||
loading={advLoading}
|
style={{ flex: 1 }}
|
||||||
allowClear
|
loading={advLoading}
|
||||||
placeholder={t("general.actions.select", "Select...")}
|
allowClear
|
||||||
popupMatchSelectWidth
|
placeholder={t("general.actions.select", "Select...")}
|
||||||
options={advisors
|
popupMatchSelectWidth
|
||||||
.map((a) => {
|
options={advisors
|
||||||
const value = getAdvisorNumber(a);
|
.map((a) => {
|
||||||
if (value == null) return null;
|
const value = getAdvisorNumber(a);
|
||||||
return { value: String(value), label: getAdvisorLabel(a) || String(value) };
|
if (value == null) return null;
|
||||||
})
|
return { value: String(value), label: getAdvisorLabel(a) || String(value) };
|
||||||
.filter(Boolean)}
|
})
|
||||||
notFoundContent={advLoading ? t("general.labels.loading") : t("general.labels.none")}
|
.filter(Boolean)}
|
||||||
/>
|
notFoundContent={advLoading ? t("general.labels.loading") : t("general.labels.none")}
|
||||||
</Form.Item>
|
/>
|
||||||
<Tooltip title={t("general.actions.refresh")}>
|
</Form.Item>
|
||||||
<Button
|
<Tooltip title={t("general.actions.refresh")}>
|
||||||
aria-label={t("general.actions.refresh")}
|
|
||||||
icon={<ReloadOutlined />}
|
|
||||||
onClick={() => fetchRrAdvisors(true)}
|
|
||||||
loading={advLoading}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Space.Compact>
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
{/* RR OpCode (prefix / base / suffix) */}
|
|
||||||
<Col xs={24} sm={12} md={12} lg={8}>
|
|
||||||
<Form.Item
|
|
||||||
required
|
|
||||||
label={
|
|
||||||
<Space size="small" align="center">
|
|
||||||
{t("jobs.fields.dms.rr_opcode", "RR OpCode")}
|
|
||||||
{isCustomOpCode && (
|
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
aria-label={t("general.actions.refresh")}
|
||||||
size="small"
|
icon={<ReloadOutlined />}
|
||||||
icon={<RollbackOutlined />}
|
onClick={() => fetchRrAdvisors(true)}
|
||||||
onClick={handleResetOpCode}
|
loading={advLoading}
|
||||||
style={{ padding: 0 }}
|
/>
|
||||||
>
|
</Tooltip>
|
||||||
{t("jobs.fields.dms.rr_opcode_reset", "Reset")}
|
</Space.Compact>
|
||||||
</Button>
|
</Form.Item>
|
||||||
)}
|
</Col>
|
||||||
</Space>
|
)}
|
||||||
}
|
|
||||||
>
|
{/* RR OpCode (prefix / base / suffix) - Only show if no early RO */}
|
||||||
<Space.Compact block>
|
{!hasEarlyRO && (
|
||||||
<Form.Item name="opPrefix" noStyle>
|
<Col xs={24} sm={12} md={12} lg={8}>
|
||||||
<Input
|
<Form.Item
|
||||||
allowClear
|
required
|
||||||
maxLength={4}
|
label={
|
||||||
style={{ width: "30%" }}
|
<Space size="small" align="center">
|
||||||
placeholder={t("jobs.fields.dms.rr_opcode_prefix", "Prefix")}
|
{t("jobs.fields.dms.rr_opcode", "RR OpCode")}
|
||||||
/>
|
{isCustomOpCode && (
|
||||||
</Form.Item>
|
<Button
|
||||||
<Form.Item
|
type="link"
|
||||||
name="opBase"
|
size="small"
|
||||||
noStyle
|
icon={<RollbackOutlined />}
|
||||||
rules={[{ required: true, message: t("general.validation.required") }]}
|
onClick={handleResetOpCode}
|
||||||
>
|
style={{ padding: 0 }}
|
||||||
<Input
|
>
|
||||||
allowClear
|
{t("jobs.fields.dms.rr_opcode_reset", "Reset")}
|
||||||
maxLength={10}
|
</Button>
|
||||||
style={{ width: "40%" }}
|
)}
|
||||||
placeholder={t("jobs.fields.dms.rr_opcode_base", "Base")}
|
</Space>
|
||||||
/>
|
}
|
||||||
</Form.Item>
|
>
|
||||||
<Form.Item name="opSuffix" noStyle>
|
<Space.Compact block>
|
||||||
<Input
|
<Form.Item name="opPrefix" noStyle>
|
||||||
allowClear
|
<Input
|
||||||
maxLength={4}
|
allowClear
|
||||||
style={{ width: "30%" }}
|
maxLength={4}
|
||||||
placeholder={t("jobs.fields.dms.rr_opcode_suffix", "Suffix")}
|
style={{ width: "30%" }}
|
||||||
/>
|
placeholder={t("jobs.fields.dms.rr_opcode_prefix", "Prefix")}
|
||||||
</Form.Item>
|
/>
|
||||||
</Space.Compact>
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item name="opBase" noStyle rules={[{ required: true }]}>
|
||||||
</Col>
|
<Input
|
||||||
|
allowClear
|
||||||
|
maxLength={10}
|
||||||
|
style={{ width: "40%" }}
|
||||||
|
placeholder={t("jobs.fields.dms.rr_opcode_base", "Base")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="opSuffix" noStyle>
|
||||||
|
<Input
|
||||||
|
allowClear
|
||||||
|
maxLength={4}
|
||||||
|
style={{ width: "30%" }}
|
||||||
|
placeholder={t("jobs.fields.dms.rr_opcode_suffix", "Suffix")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Space.Compact>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
|
||||||
<Col xs={12} sm={8} md={6} lg={4}>
|
<Col xs={12} sm={8} md={6} lg={4}>
|
||||||
<Form.Item name="kmin" label={t("jobs.fields.kmin")} initialValue={job?.kmin} rules={[{ required: true }]}>
|
<Form.Item name="kmin" label={t("jobs.fields.kmin")} initialValue={job?.kmin} rules={[{ required: true }]}>
|
||||||
@@ -355,13 +365,14 @@ export default function RRPostForm({
|
|||||||
{/* Validation */}
|
{/* Validation */}
|
||||||
<Form.Item shouldUpdate>
|
<Form.Item shouldUpdate>
|
||||||
{() => {
|
{() => {
|
||||||
const advisorOk = !!form.getFieldValue("advisorNo");
|
// When early RO exists, advisor is already set, so we don't need to validate it
|
||||||
|
const advisorOk = hasEarlyRO ? true : !!form.getFieldValue("advisorNo");
|
||||||
return (
|
return (
|
||||||
<Space size="large" wrap align="center">
|
<Space size="large" wrap align="center">
|
||||||
<Statistic title={t("jobs.labels.subtotal")} value={totals.totalSale.toFormat()} />
|
<Statistic title={t("jobs.labels.subtotal")} value={totals.totalSale.toFormat()} />
|
||||||
<Typography.Title>=</Typography.Title>
|
<Typography.Title>=</Typography.Title>
|
||||||
<Button disabled={!advisorOk} htmlType="submit">
|
<Button disabled={!advisorOk} htmlType="submit" type={hasEarlyRO ? "default" : "primary"}>
|
||||||
{t("jobs.actions.dms.post")}
|
{hasEarlyRO ? t("jobs.actions.dms.update_ro") : t("jobs.actions.dms.post")}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|||||||
367
client/src/components/dms-post-form/rr-early-ro-form.jsx
Normal file
367
client/src/components/dms-post-form/rr-early-ro-form.jsx
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
import { ReloadOutlined } from "@ant-design/icons";
|
||||||
|
import { Alert, Button, Form, Input, InputNumber, Modal, Radio, Select, Space, Table, Typography } from "antd";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
// Simple customer selector table
|
||||||
|
function CustomerSelectorTable({ customers, onSelect, isSubmitting }) {
|
||||||
|
const [selectedCustNo, setSelectedCustNo] = useState(null);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "Select",
|
||||||
|
key: "select",
|
||||||
|
width: 80,
|
||||||
|
render: (_, record) => (
|
||||||
|
<Radio checked={selectedCustNo === record.custNo} onChange={() => setSelectedCustNo(record.custNo)} />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ title: "Customer ID", dataIndex: "custNo", key: "custNo" },
|
||||||
|
{ title: "Name", dataIndex: "name", key: "name" },
|
||||||
|
{
|
||||||
|
title: "VIN Owner",
|
||||||
|
key: "vinOwner",
|
||||||
|
render: (_, record) => (record.vinOwner || record.isVehicleOwner ? "Yes" : "No")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table columns={columns} dataSource={customers} rowKey="custNo" pagination={false} size="small" />
|
||||||
|
<div style={{ marginTop: 16, display: "flex", gap: 8 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => onSelect(selectedCustNo, false)}
|
||||||
|
disabled={!selectedCustNo || isSubmitting}
|
||||||
|
loading={isSubmitting}
|
||||||
|
>
|
||||||
|
Use Selected Customer
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => onSelect(null, true)} disabled={isSubmitting} loading={isSubmitting}>
|
||||||
|
Create New Customer
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RR Early RO Creation Form
|
||||||
|
* Used from convert button or admin page to create minimal RO before full export
|
||||||
|
* @param bodyshop
|
||||||
|
* @param socket
|
||||||
|
* @param job
|
||||||
|
* @param onSuccess - callback when RO is created successfully
|
||||||
|
* @param onCancel - callback to close modal
|
||||||
|
* @param showCancelButton - whether to show cancel button
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export default function RREarlyROForm({ bodyshop, socket, job, onSuccess, onCancel, showCancelButton = true }) {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
// Advisors
|
||||||
|
const [advisors, setAdvisors] = useState([]);
|
||||||
|
const [advLoading, setAdvLoading] = useState(false);
|
||||||
|
|
||||||
|
// Customer selection
|
||||||
|
const [customerCandidates, setCustomerCandidates] = useState([]);
|
||||||
|
const [showCustomerSelector, setShowCustomerSelector] = useState(false);
|
||||||
|
|
||||||
|
// Loading and success states
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [earlyRoCreated, setEarlyRoCreated] = useState(!!job?.dms_id);
|
||||||
|
const [createdRoNumber, setCreatedRoNumber] = useState(job?.dms_id || null);
|
||||||
|
|
||||||
|
// Derive default OpCode parts from bodyshop config (matching dms.container.jsx logic)
|
||||||
|
const initialValues = useMemo(() => {
|
||||||
|
const cfg = bodyshop?.rr_configuration || {};
|
||||||
|
const defaults =
|
||||||
|
cfg.opCodeDefault ||
|
||||||
|
cfg.op_code_default ||
|
||||||
|
cfg.op_codes?.default ||
|
||||||
|
cfg.defaults?.opCode ||
|
||||||
|
cfg.defaults ||
|
||||||
|
cfg.default ||
|
||||||
|
{};
|
||||||
|
|
||||||
|
const prefix = defaults.prefix ?? defaults.opCodePrefix ?? "";
|
||||||
|
const base = defaults.base ?? defaults.opCodeBase ?? "";
|
||||||
|
const suffix = defaults.suffix ?? defaults.opCodeSuffix ?? "";
|
||||||
|
|
||||||
|
return {
|
||||||
|
kmin: job?.kmin || 0,
|
||||||
|
opPrefix: prefix,
|
||||||
|
opBase: base,
|
||||||
|
opSuffix: suffix
|
||||||
|
};
|
||||||
|
}, [bodyshop, job]);
|
||||||
|
|
||||||
|
const getAdvisorNumber = (a) => a?.advisorId;
|
||||||
|
const getAdvisorLabel = (a) => `${a?.firstName || ""} ${a?.lastName || ""}`.trim();
|
||||||
|
|
||||||
|
const fetchRrAdvisors = (refresh = false) => {
|
||||||
|
if (!socket) return;
|
||||||
|
setAdvLoading(true);
|
||||||
|
|
||||||
|
const onResult = (payload) => {
|
||||||
|
try {
|
||||||
|
const list = payload?.result ?? payload ?? [];
|
||||||
|
setAdvisors(Array.isArray(list) ? list : []);
|
||||||
|
} finally {
|
||||||
|
setAdvLoading(false);
|
||||||
|
socket.off("rr-get-advisors:result", onResult);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.once("rr-get-advisors:result", onResult);
|
||||||
|
socket.emit("rr-get-advisors", { departmentType: "B", refresh }, (ack) => {
|
||||||
|
if (ack?.ok) {
|
||||||
|
const list = ack.result ?? [];
|
||||||
|
setAdvisors(Array.isArray(list) ? list : []);
|
||||||
|
} else if (ack) {
|
||||||
|
console.error("Error fetching RR Advisors:", ack.error);
|
||||||
|
}
|
||||||
|
setAdvLoading(false);
|
||||||
|
socket.off("rr-get-advisors:result", onResult);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchRrAdvisors(false);
|
||||||
|
}, [bodyshop?.id, socket]);
|
||||||
|
|
||||||
|
const handleStartEarlyRO = async (values) => {
|
||||||
|
if (!socket) {
|
||||||
|
console.error("Socket not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
const txEnvelope = {
|
||||||
|
advisorNo: values.advisorNo,
|
||||||
|
story: values.story || "",
|
||||||
|
kmin: values.kmin || job?.kmin || 0,
|
||||||
|
opPrefix: values.opPrefix || "",
|
||||||
|
opBase: values.opBase || "",
|
||||||
|
opSuffix: values.opSuffix || ""
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emit the early RO creation request
|
||||||
|
socket.emit("rr-create-early-ro", {
|
||||||
|
jobId: job.id,
|
||||||
|
txEnvelope
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for customer selection
|
||||||
|
const customerListener = (candidates) => {
|
||||||
|
console.log("Received rr-select-customer event with candidates:", candidates);
|
||||||
|
setCustomerCandidates(candidates || []);
|
||||||
|
setShowCustomerSelector(true);
|
||||||
|
setIsSubmitting(false);
|
||||||
|
socket.off("rr-select-customer", customerListener);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.once("rr-select-customer", customerListener);
|
||||||
|
|
||||||
|
// Handle failures
|
||||||
|
const failureListener = (payload) => {
|
||||||
|
if (payload?.jobId === job.id) {
|
||||||
|
console.error("Early RO creation failed:", payload.error);
|
||||||
|
alert(`Failed to create early RO: ${payload.error}`);
|
||||||
|
setIsSubmitting(false);
|
||||||
|
setShowCustomerSelector(false);
|
||||||
|
socket.off("export-failed", failureListener);
|
||||||
|
socket.off("rr-select-customer", customerListener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.once("export-failed", failureListener);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCustomerSelected = (custNo, createNew = false) => {
|
||||||
|
if (!socket) return;
|
||||||
|
|
||||||
|
console.log("handleCustomerSelected called:", { custNo, createNew, custNoType: typeof custNo });
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setShowCustomerSelector(false);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
jobId: job.id,
|
||||||
|
custNo: createNew ? null : custNo,
|
||||||
|
create: createNew
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Emitting rr-early-customer-selected:", payload);
|
||||||
|
|
||||||
|
// Emit customer selection
|
||||||
|
socket.emit("rr-early-customer-selected", payload, (ack) => {
|
||||||
|
console.log("Received ack from rr-early-customer-selected:", ack);
|
||||||
|
setIsSubmitting(false);
|
||||||
|
|
||||||
|
if (ack?.ok) {
|
||||||
|
const roNumber = ack.dmsRoNo || ack.outsdRoNo;
|
||||||
|
setEarlyRoCreated(true);
|
||||||
|
setCreatedRoNumber(roNumber);
|
||||||
|
onSuccess?.({ roNumber, ...ack });
|
||||||
|
} else {
|
||||||
|
alert(`Failed to create early RO: ${ack?.error || "Unknown error"}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also listen for socket events
|
||||||
|
const successListener = (payload) => {
|
||||||
|
if (payload?.jobId === job.id) {
|
||||||
|
const roNumber = payload.dmsRoNo || payload.outsdRoNo;
|
||||||
|
console.log("Early RO created:", roNumber);
|
||||||
|
socket.off("rr-early-ro-created", successListener);
|
||||||
|
socket.off("export-failed", failureListener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const failureListener = (payload) => {
|
||||||
|
if (payload?.jobId === job.id) {
|
||||||
|
console.error("Early RO creation failed:", payload.error);
|
||||||
|
setIsSubmitting(false);
|
||||||
|
setEarlyRoCreated(false);
|
||||||
|
socket.off("rr-early-ro-created", successListener);
|
||||||
|
socket.off("export-failed", failureListener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.once("rr-early-ro-created", successListener);
|
||||||
|
socket.once("export-failed", failureListener);
|
||||||
|
};
|
||||||
|
|
||||||
|
// If early RO already created, show success message
|
||||||
|
if (earlyRoCreated) {
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
title="Early Reynolds RO Created"
|
||||||
|
description={`RO Number: ${createdRoNumber || "N/A"} - You can now convert the job.`}
|
||||||
|
type="success"
|
||||||
|
showIcon
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If showing customer selector, render modal
|
||||||
|
if (showCustomerSelector) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography.Title level={5}>Create Early Reynolds RO</Typography.Title>
|
||||||
|
<Typography.Paragraph type="secondary">Waiting for customer selection...</Typography.Paragraph>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="Select Customer for Early RO"
|
||||||
|
open={true}
|
||||||
|
width={800}
|
||||||
|
footer={null}
|
||||||
|
onCancel={() => {
|
||||||
|
setShowCustomerSelector(false);
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomerSelectorTable
|
||||||
|
customers={customerCandidates}
|
||||||
|
onSelect={handleCustomerSelected}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle manual submit (since we can't nest forms)
|
||||||
|
const handleManualSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
handleStartEarlyRO(values);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Validation failed:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show the form
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<Typography.Title level={5}>Create Early Reynolds RO</Typography.Title>
|
||||||
|
<Typography.Paragraph type="secondary" style={{ fontSize: "12px" }}>
|
||||||
|
Complete this section to create a minimal RO in Reynolds before converting the job.
|
||||||
|
</Typography.Paragraph>
|
||||||
|
|
||||||
|
<Form form={form} layout="vertical" component={false} initialValues={initialValues}>
|
||||||
|
<Form.Item name="advisorNo" label="Advisor" rules={[{ required: true, message: "Please select an advisor" }]}>
|
||||||
|
<Select
|
||||||
|
showSearch={{
|
||||||
|
optionFilterProp: "children",
|
||||||
|
filterOption: (input, option) => (option?.children?.toLowerCase() ?? "").includes(input.toLowerCase())
|
||||||
|
}}
|
||||||
|
loading={advLoading}
|
||||||
|
placeholder="Select advisor..."
|
||||||
|
popupRender={(menu) => (
|
||||||
|
<>
|
||||||
|
{menu}
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon={<ReloadOutlined />}
|
||||||
|
onClick={() => fetchRrAdvisors(true)}
|
||||||
|
style={{ width: "100%", textAlign: "left" }}
|
||||||
|
>
|
||||||
|
Refresh Advisors
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{advisors.map((adv) => (
|
||||||
|
<Select.Option key={getAdvisorNumber(adv)} value={getAdvisorNumber(adv)}>
|
||||||
|
{getAdvisorLabel(adv)}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="kmin"
|
||||||
|
label="Mileage In"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: "Please enter initial mileage" },
|
||||||
|
{ type: "number", min: 1, message: "Mileage must be greater than 0" }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber min={1} style={{ width: "100%" }} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{/* RR OpCode (prefix / base / suffix) */}
|
||||||
|
<Form.Item required label="RR OpCode">
|
||||||
|
<Space.Compact block>
|
||||||
|
<Form.Item name="opPrefix" noStyle>
|
||||||
|
<Input allowClear maxLength={4} style={{ width: "30%" }} placeholder="Prefix" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="opBase" noStyle rules={[{ required: true, message: "Base Required" }]}>
|
||||||
|
<Input allowClear maxLength={10} style={{ width: "40%" }} placeholder="Base" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="opSuffix" noStyle>
|
||||||
|
<Input allowClear maxLength={4} style={{ width: "30%" }} placeholder="Suffix" />
|
||||||
|
</Form.Item>
|
||||||
|
</Space.Compact>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="story" label="Comments / Story (Optional)">
|
||||||
|
<Input.TextArea rows={2} maxLength={240} showCount placeholder="Enter comments or story..." />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" onClick={handleManualSubmit} loading={isSubmitting} disabled={advLoading}>
|
||||||
|
Create Early RO
|
||||||
|
</Button>
|
||||||
|
{showCancelButton && <Button onClick={onCancel}>Cancel</Button>}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
client/src/components/dms-post-form/rr-early-ro-modal.jsx
Normal file
33
client/src/components/dms-post-form/rr-early-ro-modal.jsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Modal } from "antd";
|
||||||
|
import RREarlyROForm from "./rr-early-ro-form";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal wrapper for RR Early RO Creation Form
|
||||||
|
* @param open - boolean to control modal visibility
|
||||||
|
* @param onClose - callback when modal is closed
|
||||||
|
* @param onSuccess - callback when RO is created successfully
|
||||||
|
* @param bodyshop - bodyshop object
|
||||||
|
* @param socket - socket.io connection
|
||||||
|
* @param job - job object
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export default function RREarlyROModal({ open, onClose, onSuccess, bodyshop, socket, job }) {
|
||||||
|
const handleSuccess = (result) => {
|
||||||
|
onSuccess?.(result);
|
||||||
|
onClose?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onCancel={onClose}
|
||||||
|
footer={null}
|
||||||
|
width={700}
|
||||||
|
destroyOnHidden
|
||||||
|
title="Create Reynolds Repair Order"
|
||||||
|
>
|
||||||
|
<RREarlyROForm bodyshop={bodyshop} socket={socket} job={job} onSuccess={handleSuccess} onCancel={onClose} />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -86,11 +86,13 @@ export function EmailOverlayComponent({ emailConfig, form, selectedMediaState, b
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select
|
||||||
<Select.Option key={currentUser.email}>{currentUser.email}</Select.Option>
|
options={[
|
||||||
<Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
|
{ key: currentUser.email, value: currentUser.email, label: currentUser.email },
|
||||||
{bodyshop.md_from_emails && bodyshop.md_from_emails.map((e) => <Select.Option key={e}>{e}</Select.Option>)}
|
{ key: bodyshop.email, value: bodyshop.email, label: bodyshop.email },
|
||||||
</Select>
|
...(bodyshop.md_from_emails ? bodyshop.md_from_emails.map((e) => ({ key: e, value: e, label: e })) : [])
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={
|
label={
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Select, Space, Tag } from "antd";
|
import { Select, Space, Tag } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
//To be used as a form element only.
|
//To be used as a form element only.
|
||||||
|
|
||||||
const EmployeeSearchSelectEmail = ({ options, ...props }) => {
|
const EmployeeSearchSelectEmail = ({ options, ...props }) => {
|
||||||
@@ -12,26 +11,24 @@ const EmployeeSearchSelectEmail = ({ options, ...props }) => {
|
|||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "search"
|
optionFilterProp: "search"
|
||||||
}}
|
}}
|
||||||
// value={option}
|
|
||||||
style={{
|
style={{
|
||||||
width: 400
|
width: 400
|
||||||
}}
|
}}
|
||||||
|
options={options?.map((o) => ({
|
||||||
|
key: o.id,
|
||||||
|
value: o.user_email,
|
||||||
|
search: `${o.employee_number} ${o.first_name} ${o.last_name}`,
|
||||||
|
label: (
|
||||||
|
<Space>
|
||||||
|
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
||||||
|
<Tag color="green">
|
||||||
|
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
||||||
|
</Tag>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}))}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
/>
|
||||||
{options
|
|
||||||
? options.map((o) => (
|
|
||||||
<Option key={o.id} value={o.user_email} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
|
|
||||||
<Space>
|
|
||||||
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
|
||||||
|
|
||||||
<Tag color="green">
|
|
||||||
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
|
||||||
</Tag>
|
|
||||||
</Space>
|
|
||||||
</Option>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</Select>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default EmployeeSearchSelectEmail;
|
export default EmployeeSearchSelectEmail;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Select, Space, Tag } from "antd";
|
import { Select, Space, Tag } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
//To be used as a form element only.
|
//To be used as a form element only.
|
||||||
|
|
||||||
const EmployeeSearchSelect = ({ options, showEmail, ...props }) => {
|
const EmployeeSearchSelect = ({ options, showEmail, ...props }) => {
|
||||||
@@ -12,30 +11,29 @@ const EmployeeSearchSelect = ({ options, showEmail, ...props }) => {
|
|||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "search"
|
optionFilterProp: "search"
|
||||||
}}
|
}}
|
||||||
// value={option}
|
|
||||||
style={{
|
style={{
|
||||||
width: 400
|
width: 400
|
||||||
}}
|
}}
|
||||||
|
options={options?.map((o) => ({
|
||||||
|
key: o.id,
|
||||||
|
value: o.id,
|
||||||
|
search: `${o.employee_number} ${o.first_name} ${o.last_name}`,
|
||||||
|
label: (
|
||||||
|
<Space size="small">
|
||||||
|
{`${o.employee_number ?? ""} ${o.first_name} ${o.last_name}`}
|
||||||
|
<Tag color="green" style={{ padding: "0.1 0.1rem", marginRight: "1px", marginLeft: "1px" }}>
|
||||||
|
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
||||||
|
</Tag>
|
||||||
|
{showEmail && o.user_email ? (
|
||||||
|
<Tag color="blue" style={{ padding: "0.1 0.1rem", marginRight: "1px", marginLeft: "1px" }}>
|
||||||
|
{o.user_email}
|
||||||
|
</Tag>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}))}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
/>
|
||||||
{options
|
|
||||||
? options.map((o) => (
|
|
||||||
<Option key={o.id} value={o.id} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
|
|
||||||
<Space size="small">
|
|
||||||
{`${o.employee_number ?? ""} ${o.first_name} ${o.last_name}`}
|
|
||||||
<Tag color="green" style={{ padding: "0.1 0.1rem", marginRight: "1px", marginLeft: "1px" }}>
|
|
||||||
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
|
||||||
</Tag>
|
|
||||||
{showEmail && o.user_email ? (
|
|
||||||
<Tag color="blue" style={{ padding: "0.1 0.1rem", marginRight: "1px", marginLeft: "1px" }}>
|
|
||||||
{o.user_email}
|
|
||||||
</Tag>
|
|
||||||
) : null}
|
|
||||||
</Space>
|
|
||||||
</Option>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</Select>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default EmployeeSearchSelect;
|
export default EmployeeSearchSelect;
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ export default function GlobalSearch() {
|
|||||||
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
|
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const executeSearch = (v) => {
|
const executeSearch = (variables) => {
|
||||||
if (v && v.variables.search && v.variables.search !== "" && v.variables.search.length >= 3) callSearch(v);
|
if (variables?.search !== "" && variables?.search?.length >= 3)
|
||||||
|
callSearch({
|
||||||
|
variables
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||||
|
|
||||||
@@ -157,7 +160,9 @@ export default function GlobalSearch() {
|
|||||||
return (
|
return (
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
options={options}
|
options={options}
|
||||||
onSearch={handleSearch}
|
showSearch={{
|
||||||
|
onSearch: handleSearch
|
||||||
|
}}
|
||||||
defaultActiveFirstOption
|
defaultActiveFirstOption
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key !== "Enter") return;
|
if (e.key !== "Enter") return;
|
||||||
|
|||||||
@@ -67,16 +67,19 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInsSelect = (value, option) => {
|
const handleInsSelect = (value) => {
|
||||||
form.setFieldsValue({
|
const selectedVendor = bodyshop.md_ins_cos.find(s => s.name === value);
|
||||||
addr1: option.obj.name,
|
if (selectedVendor) {
|
||||||
addr2: option.obj.street1,
|
form.setFieldsValue({
|
||||||
addr3: option.obj.street2,
|
addr1: selectedVendor.name,
|
||||||
city: option.obj.city,
|
addr2: selectedVendor.street1,
|
||||||
state: option.obj.state,
|
addr3: selectedVendor.street2,
|
||||||
zip: option.obj.zip,
|
city: selectedVendor.city,
|
||||||
vendorid: null
|
state: selectedVendor.state,
|
||||||
});
|
zip: selectedVendor.zip,
|
||||||
|
vendorid: null
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVendorSelect = (vendorid) => {
|
const handleVendorSelect = (vendorid) => {
|
||||||
@@ -97,19 +100,19 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={showModal}>{t("printcenter.jobs.3rdpartypayer")}</Button>
|
<Button onClick={showModal}>{t("printcenter.jobs.3rdpartypayer")}</Button>
|
||||||
<Modal open={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
|
<Modal open={isModalVisible} onOk={handleOk} onCancel={handleCancel} getContainer={() => document.body}>
|
||||||
<Form onFinish={handleFinish} autoComplete={"off"} layout="vertical" form={form}>
|
<Form onFinish={handleFinish} autoComplete={"off"} layout="vertical" form={form}>
|
||||||
<Form.Item label={t("bills.fields.vendor")} name="vendorid">
|
<Form.Item label={t("bills.fields.vendor")} name="vendorid">
|
||||||
<VendorSearchSelect options={VendorAutoCompleteData?.vendors} onSelect={handleVendorSelect} />
|
<VendorSearchSelect options={VendorAutoCompleteData?.vendors} onSelect={handleVendorSelect} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("bodyshop.fields.md_ins_co.name")} name="ins_co_id">
|
<Form.Item label={t("bodyshop.fields.md_ins_co.name")} name="ins_co_id">
|
||||||
<Select onSelect={handleInsSelect}>
|
<Select
|
||||||
{bodyshop.md_ins_cos.map((s) => (
|
onSelect={handleInsSelect}
|
||||||
<Select.Option key={s.name} obj={s} value={s.name}>
|
options={bodyshop.md_ins_cos.map((s) => ({
|
||||||
{s.name}
|
value: s.name,
|
||||||
</Select.Option>
|
label: s.name
|
||||||
))}
|
}))}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
<Form.Item label={t("printcenter.jobs.3rdpartyfields.addr1")} name="addr1">
|
<Form.Item label={t("printcenter.jobs.3rdpartyfields.addr1")} name="addr1">
|
||||||
|
|||||||
@@ -88,17 +88,15 @@ export function JoblineBulkAssign({ setSelectedLines, selectedLines, insertAudit
|
|||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "children",
|
optionFilterProp: "label",
|
||||||
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
filterOption: (input, option) => option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
>
|
options={bodyshop.employee_teams.map((team) => ({
|
||||||
{bodyshop.employee_teams.map((team) => (
|
value: team.id,
|
||||||
<Select.Option value={team.id} key={team.id} name={team.name}>
|
label: team.name
|
||||||
{team.name}
|
}))}
|
||||||
</Select.Option>
|
/>
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|||||||
@@ -122,22 +122,26 @@ export function JobLineConvertToLabor({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select allowClear showSearch={{ optionFilterProp: "children" }}>
|
<Select
|
||||||
<Select.Option value="LAA">{t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
allowClear
|
||||||
<Select.Option value="LAB">{t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
showSearch
|
||||||
<Select.Option value="LAD">{t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
options={[
|
||||||
<Select.Option value="LAE">{t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
{ value: "LAA", label: t("joblines.fields.lbr_types.LAA") },
|
||||||
<Select.Option value="LAF">{t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
{ value: "LAB", label: t("joblines.fields.lbr_types.LAB") },
|
||||||
<Select.Option value="LAG">{t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
{ value: "LAD", label: t("joblines.fields.lbr_types.LAD") },
|
||||||
<Select.Option value="LAM">{t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
{ value: "LAE", label: t("joblines.fields.lbr_types.LAE") },
|
||||||
<Select.Option value="LAR">{t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
{ value: "LAF", label: t("joblines.fields.lbr_types.LAF") },
|
||||||
<Select.Option value="LAS">{t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
{ value: "LAG", label: t("joblines.fields.lbr_types.LAG") },
|
||||||
<Select.Option value="LAU">{t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
{ value: "LAM", label: t("joblines.fields.lbr_types.LAM") },
|
||||||
<Select.Option value="LA1">{t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
{ value: "LAR", label: t("joblines.fields.lbr_types.LAR") },
|
||||||
<Select.Option value="LA2">{t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
{ value: "LAS", label: t("joblines.fields.lbr_types.LAS") },
|
||||||
<Select.Option value="LA3">{t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
{ value: "LAU", label: t("joblines.fields.lbr_types.LAU") },
|
||||||
<Select.Option value="LA4">{t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
{ value: "LA1", label: t("joblines.fields.lbr_types.LA1") },
|
||||||
</Select>
|
{ value: "LA2", label: t("joblines.fields.lbr_types.LA2") },
|
||||||
|
{ value: "LA3", label: t("joblines.fields.lbr_types.LA3") },
|
||||||
|
{ value: "LA4", label: t("joblines.fields.lbr_types.LA4") }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item shouldUpdate>
|
<Form.Item shouldUpdate>
|
||||||
|
|||||||
@@ -115,19 +115,18 @@ export function JobLineDispatchButton({
|
|||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "children",
|
optionFilterProp: "label",
|
||||||
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
filterOption: (input, option) => option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
>
|
options={bodyshop.employees
|
||||||
{bodyshop.employees
|
|
||||||
.filter((emp) => emp.active)
|
.filter((emp) => emp.active)
|
||||||
.map((emp) => (
|
.map((emp) => ({
|
||||||
<Select.Option value={emp.id} key={emp.id} name={`${emp.first_name} ${emp.last_name}`}>
|
value: emp.id,
|
||||||
{`${emp.first_name} ${emp.last_name}`}
|
key: emp.id,
|
||||||
</Select.Option>
|
label: `${emp.first_name} ${emp.last_name}`
|
||||||
))}
|
}))}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|||||||
@@ -64,13 +64,12 @@ export function JobLineStatusPopup({ bodyshop, jobline, disabled }) {
|
|||||||
onSelect={handleChange}
|
onSelect={handleChange}
|
||||||
onBlur={handleSave}
|
onBlur={handleSave}
|
||||||
onClear={() => handleChange(null)}
|
onClear={() => handleChange(null)}
|
||||||
>
|
options={Object.values(bodyshop.md_order_statuses).map((s, idx) => ({
|
||||||
{Object.values(bodyshop.md_order_statuses).map((s, idx) => (
|
key: idx,
|
||||||
<Select.Option key={idx} value={s}>
|
value: s,
|
||||||
{s}
|
label: s
|
||||||
</Select.Option>
|
}))}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
</LoadingSpinner>
|
</LoadingSpinner>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -75,13 +75,12 @@ export function JoblineTeamAssignment({ bodyshop, jobline, disabled, jobId, inse
|
|||||||
onSelect={handleChange}
|
onSelect={handleChange}
|
||||||
onBlur={handleSave}
|
onBlur={handleSave}
|
||||||
onClear={() => handleChange(null)}
|
onClear={() => handleChange(null)}
|
||||||
>
|
options={Object.values(bodyshop.employee_teams).map((s) => ({
|
||||||
{Object.values(bodyshop.employee_teams).map((s, idx) => (
|
key: s.id,
|
||||||
<Select.Option key={idx} value={s.id}>
|
value: s.id,
|
||||||
{s.name}
|
label: s.name
|
||||||
</Select.Option>
|
}))}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
</LoadingSpinner>
|
</LoadingSpinner>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -67,22 +67,22 @@ export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCa
|
|||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
<Form.Item label={t("joblines.fields.mod_lbr_ty")} name="mod_lbr_ty">
|
<Form.Item label={t("joblines.fields.mod_lbr_ty")} name="mod_lbr_ty">
|
||||||
<Select allowClear>
|
<Select allowClear options={[
|
||||||
<Select.Option value="LAA">{t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
{ value: "LAA", label: t("joblines.fields.lbr_types.LAA") },
|
||||||
<Select.Option value="LAB">{t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
{ value: "LAB", label: t("joblines.fields.lbr_types.LAB") },
|
||||||
<Select.Option value="LAD">{t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
{ value: "LAD", label: t("joblines.fields.lbr_types.LAD") },
|
||||||
<Select.Option value="LAE">{t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
{ value: "LAE", label: t("joblines.fields.lbr_types.LAE") },
|
||||||
<Select.Option value="LAF">{t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
{ value: "LAF", label: t("joblines.fields.lbr_types.LAF") },
|
||||||
<Select.Option value="LAG">{t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
{ value: "LAG", label: t("joblines.fields.lbr_types.LAG") },
|
||||||
<Select.Option value="LAM">{t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
{ value: "LAM", label: t("joblines.fields.lbr_types.LAM") },
|
||||||
<Select.Option value="LAR">{t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
{ value: "LAR", label: t("joblines.fields.lbr_types.LAR") },
|
||||||
<Select.Option value="LAS">{t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
{ value: "LAS", label: t("joblines.fields.lbr_types.LAS") },
|
||||||
<Select.Option value="LAU">{t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
{ value: "LAU", label: t("joblines.fields.lbr_types.LAU") },
|
||||||
<Select.Option value="LA1">{t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
{ value: "LA1", label: t("joblines.fields.lbr_types.LA1") },
|
||||||
<Select.Option value="LA2">{t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
{ value: "LA2", label: t("joblines.fields.lbr_types.LA2") },
|
||||||
<Select.Option value="LA3">{t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
{ value: "LA3", label: t("joblines.fields.lbr_types.LA3") },
|
||||||
<Select.Option value="LA4">{t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
{ value: "LA4", label: t("joblines.fields.lbr_types.LA4") }
|
||||||
</Select>
|
]} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("joblines.fields.op_code_desc")} name="op_code_desc">
|
<Form.Item label={t("joblines.fields.op_code_desc")} name="op_code_desc">
|
||||||
<Input />
|
<Input />
|
||||||
@@ -128,17 +128,17 @@ export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCa
|
|||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow>
|
<LayoutFormRow>
|
||||||
<Form.Item label={t("joblines.fields.part_type")} name="part_type">
|
<Form.Item label={t("joblines.fields.part_type")} name="part_type">
|
||||||
<Select allowClear>
|
<Select allowClear options={[
|
||||||
<Select.Option value="PAA">{t("joblines.fields.part_types.PAA")}</Select.Option>
|
{ value: "PAA", label: t("joblines.fields.part_types.PAA") },
|
||||||
<Select.Option value="PAC">{t("joblines.fields.part_types.PAC")}</Select.Option>
|
{ value: "PAC", label: t("joblines.fields.part_types.PAC") },
|
||||||
<Select.Option value="PAE">{t("joblines.fields.part_types.PAE")}</Select.Option>
|
{ value: "PAE", label: t("joblines.fields.part_types.PAE") },
|
||||||
<Select.Option value="PAL">{t("joblines.fields.part_types.PAL")}</Select.Option>
|
{ value: "PAL", label: t("joblines.fields.part_types.PAL") },
|
||||||
<Select.Option value="PAM">{t("joblines.fields.part_types.PAM")}</Select.Option>
|
{ value: "PAM", label: t("joblines.fields.part_types.PAM") },
|
||||||
<Select.Option value="PAN">{t("joblines.fields.part_types.PAN")}</Select.Option>
|
{ value: "PAN", label: t("joblines.fields.part_types.PAN") },
|
||||||
<Select.Option value="PAO">{t("joblines.fields.part_types.PAO")}</Select.Option>
|
{ value: "PAO", label: t("joblines.fields.part_types.PAO") },
|
||||||
<Select.Option value="PAR">{t("joblines.fields.part_types.PAR")}</Select.Option>
|
{ value: "PAR", label: t("joblines.fields.part_types.PAR") },
|
||||||
<Select.Option value="PAS">{t("joblines.fields.part_types.PAS")}</Select.Option>
|
{ value: "PAS", label: t("joblines.fields.part_types.PAS") }
|
||||||
</Select>
|
]} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("joblines.fields.oem_partno")} name="oem_partno">
|
<Form.Item label={t("joblines.fields.oem_partno")} name="oem_partno">
|
||||||
<Input />
|
<Input />
|
||||||
|
|||||||
@@ -1,29 +1,65 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Tag, Tooltip } from "antd";
|
import { Tooltip } from "antd";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = () => ({
|
const mapDispatchToProps = () => ({});
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
const colorMap = {
|
||||||
|
gray: { bg: "#fafafa", border: "#d9d9d9", text: "#000000" },
|
||||||
|
gold: { bg: "#fffbe6", border: "#ffe58f", text: "#d48806" },
|
||||||
|
red: { bg: "#fff1f0", border: "#ffccc7", text: "#cf1322" },
|
||||||
|
blue: { bg: "#e6f7ff", border: "#91d5ff", text: "#0958d9" },
|
||||||
|
green: { bg: "#f6ffed", border: "#b7eb8f", text: "#389e0d" },
|
||||||
|
orange: { bg: "#fff7e6", border: "#ffd591", text: "#d46b08" }
|
||||||
|
};
|
||||||
|
|
||||||
|
function CompactTag({ color = "gray", children, tooltip = "" }) {
|
||||||
|
const colors = colorMap[color] || colorMap.gray;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: "0 2px",
|
||||||
|
fontSize: "12px",
|
||||||
|
lineHeight: "20px",
|
||||||
|
backgroundColor: colors.bg,
|
||||||
|
border: `1px solid ${colors.border}`,
|
||||||
|
borderRadius: "2px",
|
||||||
|
color: colors.text,
|
||||||
|
minWidth: "24px",
|
||||||
|
textAlign: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip title={tooltip}>{children}</Tooltip>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobPartsQueueCount);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobPartsQueueCount);
|
||||||
|
|
||||||
export function JobPartsQueueCount({ bodyshop, parts }) {
|
export function JobPartsQueueCount({ bodyshop, parts }) {
|
||||||
const { t } = useTranslation();
|
|
||||||
const partsStatus = useMemo(() => {
|
const partsStatus = useMemo(() => {
|
||||||
if (!parts) return null;
|
if (!parts) return null;
|
||||||
|
|
||||||
const statusKeys = ["default_bo", "default_ordered", "default_received", "default_returned"];
|
const statusKeys = ["default_bo", "default_ordered", "default_received", "default_returned"];
|
||||||
|
|
||||||
return parts.reduce(
|
return parts.reduce(
|
||||||
(acc, val) => {
|
(acc, val) => {
|
||||||
if (val.part_type === "PAS" || val.part_type === "PASL") return acc;
|
if (val.part_type === "PAS" || val.part_type === "PASL") return acc;
|
||||||
acc.total = acc.total + val.count;
|
|
||||||
acc[val.status] = acc[val.status] + val.count;
|
acc.total += val.count;
|
||||||
|
|
||||||
|
// NOTE: if val.status is null, object key becomes "null"
|
||||||
|
acc[val.status] = (acc[val.status] ?? 0) + val.count;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -34,45 +70,38 @@ export function JobPartsQueueCount({ bodyshop, parts }) {
|
|||||||
);
|
);
|
||||||
}, [bodyshop, parts]);
|
}, [bodyshop, parts]);
|
||||||
|
|
||||||
if (!parts) return null;
|
if (!parts || !partsStatus) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "grid",
|
display: "inline-flex", // ✅ shrink-wraps, fixes the “extra box” to the right
|
||||||
gridTemplateColumns: "repeat(auto-fit, minmax(40px, 1fr))",
|
gap: 2,
|
||||||
gap: "8px",
|
alignItems: "center",
|
||||||
width: "100%",
|
whiteSpace: "nowrap"
|
||||||
justifyItems: "start"
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip title="Total">
|
<CompactTag tooltip="Total" color="gray">
|
||||||
<Tag style={{ minWidth: "40px", textAlign: "center" }}>{partsStatus.total}</Tag>
|
{partsStatus.total}
|
||||||
</Tooltip>
|
</CompactTag>
|
||||||
<Tooltip title={t("dashboard.errors.status_normal")}>
|
|
||||||
<Tag color="gold" style={{ minWidth: "40px", textAlign: "center" }}>
|
<CompactTag tooltip="No Status" color="gold">
|
||||||
{partsStatus["null"]}
|
{partsStatus["null"]}
|
||||||
</Tag>
|
</CompactTag>
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_bo}>
|
<CompactTag tooltip={bodyshop.md_order_statuses.default_bo} color="red">
|
||||||
<Tag color="red" style={{ minWidth: "40px", textAlign: "center" }}>
|
{partsStatus[bodyshop.md_order_statuses.default_bo]}
|
||||||
{partsStatus[bodyshop.md_order_statuses.default_bo]}
|
</CompactTag>
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
<CompactTag tooltip={bodyshop.md_order_statuses.default_ordered} color="blue">
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_ordered}>
|
{partsStatus[bodyshop.md_order_statuses.default_ordered]}
|
||||||
<Tag color="blue" style={{ minWidth: "40px", textAlign: "center" }}>
|
</CompactTag>
|
||||||
{partsStatus[bodyshop.md_order_statuses.default_ordered]}
|
<CompactTag tooltip={bodyshop.md_order_statuses.default_received} color="green">
|
||||||
</Tag>
|
{partsStatus[bodyshop.md_order_statuses.default_received]}
|
||||||
</Tooltip>
|
</CompactTag>
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_received}>
|
<CompactTag tooltip={bodyshop.md_order_statuses.default_returned} color="orange">
|
||||||
<Tag color="green" style={{ minWidth: "40px", textAlign: "center" }}>
|
{partsStatus[bodyshop.md_order_statuses.default_returned]}
|
||||||
{partsStatus[bodyshop.md_order_statuses.default_received]}
|
</CompactTag>
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_returned}>
|
|
||||||
<Tag color="orange" style={{ minWidth: "40px", textAlign: "center" }}>
|
|
||||||
{partsStatus[bodyshop.md_order_statuses.default_returned]}
|
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,17 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
* @param parts
|
* @param parts
|
||||||
* @param displayMode
|
* @param displayMode
|
||||||
* @param popoverPlacement
|
* @param popoverPlacement
|
||||||
|
* @param countsOnly
|
||||||
* @returns {JSX.Element}
|
* @returns {JSX.Element}
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export function JobPartsReceived({ bodyshop, parts, displayMode = "full", popoverPlacement = "top" }) {
|
export function JobPartsReceived({
|
||||||
|
bodyshop,
|
||||||
|
parts,
|
||||||
|
displayMode = "full",
|
||||||
|
popoverPlacement = "top",
|
||||||
|
countsOnly = false
|
||||||
|
}) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -61,6 +68,8 @@ export function JobPartsReceived({ bodyshop, parts, displayMode = "full", popove
|
|||||||
[canOpen]
|
[canOpen]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (countsOnly) return <JobPartsQueueCount parts={parts} />;
|
||||||
|
|
||||||
const displayText =
|
const displayText =
|
||||||
displayMode === "compact" ? summary.percentLabel : `${summary.percentLabel} (${summary.received}/${summary.total})`;
|
displayMode === "compact" ? summary.percentLabel : `${summary.percentLabel} (${summary.received}/${summary.total})`;
|
||||||
|
|
||||||
@@ -74,7 +83,7 @@ export function JobPartsReceived({ bodyshop, parts, displayMode = "full", popove
|
|||||||
trigger={["click"]}
|
trigger={["click"]}
|
||||||
placement={popoverPlacement}
|
placement={popoverPlacement}
|
||||||
content={
|
content={
|
||||||
<div onClick={stop} style={{ minWidth: 260 }}>
|
<div onClick={stop}>
|
||||||
<JobPartsQueueCount parts={parts} />
|
<JobPartsQueueCount parts={parts} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -99,7 +108,8 @@ JobPartsReceived.propTypes = {
|
|||||||
bodyshop: PropTypes.object,
|
bodyshop: PropTypes.object,
|
||||||
parts: PropTypes.array,
|
parts: PropTypes.array,
|
||||||
displayMode: PropTypes.oneOf(["full", "compact"]),
|
displayMode: PropTypes.oneOf(["full", "compact"]),
|
||||||
popoverPlacement: PropTypes.string
|
popoverPlacement: PropTypes.string,
|
||||||
|
countsOnly: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps)(JobPartsReceived);
|
export default connect(mapStateToProps)(JobPartsReceived);
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import { SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE } from
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const JobSearchSelect = ({
|
const JobSearchSelect = ({
|
||||||
disabled,
|
disabled,
|
||||||
convertedOnly = false,
|
convertedOnly = false,
|
||||||
@@ -87,24 +85,24 @@ const JobSearchSelect = ({
|
|||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
suffixIcon={(loading || idLoading) && <Spin />} // matches OLD spinner semantics
|
suffixIcon={(loading || idLoading) && <Spin />} // matches OLD spinner semantics
|
||||||
notFoundContent={loading ? <LoadingOutlined /> : null} // matches OLD (loading only)
|
notFoundContent={loading ? <LoadingOutlined /> : null} // matches OLD (loading only)
|
||||||
>
|
options={theOptions?.map((o) => ({
|
||||||
{theOptions
|
key: o.id,
|
||||||
? theOptions.map((o) => (
|
value: o.id,
|
||||||
<Option key={o.id} value={o.id} status={o.status}>
|
status: o.status,
|
||||||
<Space align="center">
|
label: (
|
||||||
<span>
|
<Space align="center">
|
||||||
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${o.ro_number || t("general.labels.na")} | ${OwnerNameDisplayFunction(
|
<span>
|
||||||
o
|
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${o.ro_number || t("general.labels.na")} | ${OwnerNameDisplayFunction(
|
||||||
)} | ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${o.v_model_desc || ""}`}
|
o
|
||||||
</span>
|
)} | ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${o.v_model_desc || ""}`}
|
||||||
<Tag>
|
</span>
|
||||||
<strong>{o.status}</strong>
|
<Tag>
|
||||||
</Tag>
|
<strong>{o.status}</strong>
|
||||||
</Space>
|
</Tag>
|
||||||
</Option>
|
</Space>
|
||||||
))
|
)
|
||||||
: null}
|
}))}
|
||||||
</Select>
|
/>
|
||||||
|
|
||||||
{error ? <AlertComponent title={error.message} type="error" /> : null}
|
{error ? <AlertComponent title={error.message} type="error" /> : null}
|
||||||
{idError ? <AlertComponent title={idError.message} type="error" /> : null}
|
{idError ? <AlertComponent title={idError.message} type="error" /> : null}
|
||||||
|
|||||||
@@ -59,13 +59,12 @@ export function JobsAdminClass({ bodyshop, job }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select
|
||||||
{bodyshop.md_classes.map((s) => (
|
options={bodyshop.md_classes.map((s) => ({
|
||||||
<Select.Option key={s} value={s}>
|
value: s,
|
||||||
{s}
|
label: s
|
||||||
</Select.Option>
|
}))}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<tr key={field.key}>
|
<tr key={field.key}>
|
||||||
{/* Hidden field to preserve jobline ID */}
|
|
||||||
<Form.Item hidden name={[field.name, "id"]}>
|
|
||||||
<input />
|
|
||||||
</Form.Item>
|
|
||||||
<td>
|
<td>
|
||||||
|
{/* Hidden field to preserve jobline ID without injecting a div under <tr> */}
|
||||||
|
<Form.Item noStyle name={[field.name, "id"]}>
|
||||||
|
<input type="hidden" />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
// label={t("joblines.fields.line_desc")}
|
// label={t("joblines.fields.line_desc")}
|
||||||
key={`${index}line_desc`}
|
key={`${index}line_desc`}
|
||||||
@@ -141,13 +141,11 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
|||||||
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
disabled={jobRO}
|
disabled={jobRO}
|
||||||
>
|
options={bodyshop.md_responsibility_centers.profits.map((p) => ({
|
||||||
{bodyshop.md_responsibility_centers.profits.map((p) => (
|
value: p.name,
|
||||||
<Select.Option key={p.name} value={p.name}>
|
label: p.name
|
||||||
{p.name}
|
}))}
|
||||||
</Select.Option>
|
/>
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -171,13 +169,11 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
|||||||
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
disabled={jobRO}
|
disabled={jobRO}
|
||||||
>
|
options={bodyshop.md_responsibility_centers.profits.map((p) => ({
|
||||||
{bodyshop.md_responsibility_centers.profits.map((p) => (
|
value: p.name,
|
||||||
<Select.Option key={p.name} value={p.name}>
|
label: p.name
|
||||||
{p.name}
|
}))}
|
||||||
</Select.Option>
|
/>
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
import { useMutation } from "@apollo/client/react";
|
import { useMutation } from "@apollo/client/react";
|
||||||
import { Button, Form, Input, Popover, Select, Space, Switch } from "antd";
|
import { Button, Divider, Form, Input, Modal, Select, Space, Switch } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { some } from "lodash";
|
import { some } from "lodash";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
||||||
import { CONVERT_JOB_TO_RO } from "../../graphql/jobs.queries";
|
import { CONVERT_JOB_TO_RO } from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import { DMS_MAP, getDmsMode } from "../../utils/dmsUtils";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
import RREarlyROForm from "../dms-post-form/rr-early-ro-form";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
jobRO: selectJobReadOnly
|
jobRO: selectJobReadOnly
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -33,18 +37,83 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTrail, parentFormIsFieldsTouched }) {
|
export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTrail, parentFormIsFieldsTouched }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const [earlyRoCreated, setEarlyRoCreated] = useState(!!job?.dms_id);
|
||||||
|
const [earlyRoCreatedThisSession, setEarlyRoCreatedThisSession] = useState(false);
|
||||||
|
|
||||||
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
|
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
const allFormValues = Form.useWatch([], form);
|
const allFormValues = Form.useWatch([], form);
|
||||||
|
const { socket } = useSocket();
|
||||||
|
|
||||||
|
const {
|
||||||
|
treatments: { Fortellis }
|
||||||
|
} = useTreatmentsWithConfig({
|
||||||
|
attributes: {},
|
||||||
|
names: ["Fortellis"],
|
||||||
|
splitKey: bodyshop?.imexshopid
|
||||||
|
});
|
||||||
|
|
||||||
|
const dmsMode = getDmsMode(bodyshop, Fortellis.treatment);
|
||||||
|
const isReynoldsMode = dmsMode === DMS_MAP.reynolds;
|
||||||
|
|
||||||
|
const insuranceOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
(bodyshop?.md_ins_cos ?? []).map((s) => ({
|
||||||
|
value: s.name,
|
||||||
|
label: s.name
|
||||||
|
})),
|
||||||
|
[bodyshop?.md_ins_cos]
|
||||||
|
);
|
||||||
|
|
||||||
|
const classOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
(bodyshop?.md_classes ?? []).map((s) => ({
|
||||||
|
value: s,
|
||||||
|
label: s
|
||||||
|
})),
|
||||||
|
[bodyshop?.md_classes]
|
||||||
|
);
|
||||||
|
|
||||||
|
const referralOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
(bodyshop?.md_referral_sources ?? []).map((s) => ({
|
||||||
|
value: s,
|
||||||
|
label: s
|
||||||
|
})),
|
||||||
|
[bodyshop?.md_referral_sources]
|
||||||
|
);
|
||||||
|
|
||||||
|
const csrOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
(bodyshop?.employees ?? [])
|
||||||
|
.filter((emp) => emp.active)
|
||||||
|
.map((emp) => ({
|
||||||
|
value: emp.id,
|
||||||
|
label: `${emp.first_name} ${emp.last_name}`
|
||||||
|
})),
|
||||||
|
[bodyshop?.employees]
|
||||||
|
);
|
||||||
|
|
||||||
|
const categoryOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
(bodyshop?.md_categories ?? []).map((s) => ({
|
||||||
|
value: s,
|
||||||
|
label: s
|
||||||
|
})),
|
||||||
|
[bodyshop?.md_categories]
|
||||||
|
);
|
||||||
|
|
||||||
const handleConvert = async ({ employee_csr, category, ...values }) => {
|
const handleConvert = async ({ employee_csr, category, ...values }) => {
|
||||||
if (parentFormIsFieldsTouched()) {
|
if (parentFormIsFieldsTouched()) {
|
||||||
alert(t("jobs.labels.savebeforeconversion"));
|
alert(t("jobs.labels.savebeforeconversion"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const res = await mutationConvertJob({
|
const res = await mutationConvertJob({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
@@ -58,13 +127,11 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (values.ca_gst_registrant) {
|
if (values.ca_gst_registrant) {
|
||||||
await axios.post("/job/totalsssu", {
|
await axios.post("/job/totalsssu", { id: job.id });
|
||||||
id: job.id
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res.errors) {
|
if (!res.errors) {
|
||||||
refetch();
|
refetch?.();
|
||||||
notification.success({
|
notification.success({
|
||||||
title: t("jobs.successes.converted")
|
title: t("jobs.successes.converted")
|
||||||
});
|
});
|
||||||
@@ -77,182 +144,183 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitDisabled = useCallback(() => some(allFormValues, (v) => v === undefined), [allFormValues]);
|
const submitDisabled = useCallback(() => some(allFormValues, (v) => v === undefined), [allFormValues]);
|
||||||
|
|
||||||
const popMenu = (
|
const handleEarlyROSuccess = (result) => {
|
||||||
<div>
|
setEarlyRoCreated(true);
|
||||||
<Form
|
setEarlyRoCreatedThisSession(true);
|
||||||
layout="vertical"
|
notification.success({
|
||||||
form={form}
|
title: t("jobs.successes.early_ro_created"),
|
||||||
onFinish={handleConvert}
|
description: `RO Number: ${result.roNumber || "N/A"}`
|
||||||
initialValues={{
|
});
|
||||||
driveable: true,
|
|
||||||
towin: job.towin,
|
setTimeout(() => {
|
||||||
ca_gst_registrant: job.ca_gst_registrant,
|
refetch?.();
|
||||||
employee_csr: job.employee_csr,
|
}, 2000);
|
||||||
category: job.category,
|
};
|
||||||
referral_source: job.referral_source,
|
|
||||||
referral_source_extra: job.referral_source_extra ?? ""
|
const handleModalClose = () => {
|
||||||
}}
|
setOpen(false);
|
||||||
>
|
};
|
||||||
<Form.Item
|
|
||||||
name={["ins_co_nm"]}
|
|
||||||
label={t("jobs.fields.ins_co_nm")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select showSearch>
|
|
||||||
{bodyshop.md_ins_cos.map((s, i) => (
|
|
||||||
<Select.Option key={i} value={s.name}>
|
|
||||||
{s.name}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
{bodyshop.enforce_class && (
|
|
||||||
<Form.Item
|
|
||||||
name={"class"}
|
|
||||||
label={t("jobs.fields.class")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: bodyshop.enforce_class
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select>
|
|
||||||
{bodyshop.md_classes.map((s) => (
|
|
||||||
<Select.Option key={s} value={s}>
|
|
||||||
{s}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{bodyshop.enforce_referral && (
|
|
||||||
<>
|
|
||||||
<Form.Item
|
|
||||||
name={"referral_source"}
|
|
||||||
label={t("jobs.fields.referralsource")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: bodyshop.enforce_referral
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select>
|
|
||||||
{bodyshop.md_referral_sources.map((s) => (
|
|
||||||
<Select.Option key={s} value={s}>
|
|
||||||
{s}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{bodyshop.enforce_conversion_csr && (
|
|
||||||
<Form.Item
|
|
||||||
name={"employee_csr"}
|
|
||||||
label={t(
|
|
||||||
InstanceRenderManager({
|
|
||||||
imex: "jobs.fields.employee_csr",
|
|
||||||
rome: "jobs.fields.employee_csr_writer"
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: bodyshop.enforce_conversion_csr
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
showSearch={{
|
|
||||||
optionFilterProp: "children",
|
|
||||||
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
|
||||||
}}
|
|
||||||
style={{ width: 200 }}
|
|
||||||
>
|
|
||||||
{bodyshop.employees
|
|
||||||
.filter((emp) => emp.active)
|
|
||||||
.map((emp) => (
|
|
||||||
<Select.Option value={emp.id} key={emp.id} name={`${emp.first_name} ${emp.last_name}`}>
|
|
||||||
{`${emp.first_name} ${emp.last_name}`}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{bodyshop.enforce_conversion_category && (
|
|
||||||
<Form.Item
|
|
||||||
name={"category"}
|
|
||||||
label={t("jobs.fields.category")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: bodyshop.enforce_conversion_category
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select allowClear>
|
|
||||||
{bodyshop.md_categories.map((s) => (
|
|
||||||
<Select.Option key={s} value={s}>
|
|
||||||
{s}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
|
|
||||||
<Form.Item label={t("jobs.fields.ca_gst_registrant")} name="ca_gst_registrant" valuePropName="checked">
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
<Form.Item label={t("jobs.fields.driveable")} name="driveable" valuePropName="checked">
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t("jobs.fields.towin")} name="towin" valuePropName="checked">
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Space wrap>
|
|
||||||
<Button disabled={submitDisabled()} type="primary" danger onClick={() => form.submit()} loading={loading}>
|
|
||||||
{t("jobs.actions.convert")}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
|
||||||
</Space>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (job.converted) return <></>;
|
if (job.converted) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} content={popMenu}>
|
<>
|
||||||
<Button
|
<Button
|
||||||
key="convert"
|
key="convert"
|
||||||
type="primary"
|
type="primary"
|
||||||
danger
|
danger
|
||||||
// style={{ display: job.converted ? "none" : "" }}
|
|
||||||
disabled={job.converted || jobRO}
|
disabled={job.converted || jobRO}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setEarlyRoCreated(!!job?.dms_id);
|
||||||
|
setEarlyRoCreatedThisSession(false);
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("jobs.actions.convert")}
|
{t("jobs.actions.convert")}
|
||||||
</Button>
|
</Button>
|
||||||
</Popover>
|
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onCancel={handleModalClose}
|
||||||
|
closable={!(earlyRoCreatedThisSession && !job.converted)}
|
||||||
|
maskClosable={!(earlyRoCreatedThisSession && !job.converted)}
|
||||||
|
title={t("jobs.actions.convert")}
|
||||||
|
footer={null}
|
||||||
|
width={700}
|
||||||
|
destroyOnHidden
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
form={form}
|
||||||
|
preserve={false}
|
||||||
|
onFinish={handleConvert}
|
||||||
|
initialValues={{
|
||||||
|
driveable: true,
|
||||||
|
towin: job.towin,
|
||||||
|
ca_gst_registrant: job.ca_gst_registrant,
|
||||||
|
employee_csr: job.employee_csr,
|
||||||
|
category: job.category,
|
||||||
|
referral_source: job.referral_source,
|
||||||
|
referral_source_extra: job.referral_source_extra ?? ""
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isReynoldsMode && !job.dms_id && !earlyRoCreated && (
|
||||||
|
<>
|
||||||
|
<RREarlyROForm
|
||||||
|
bodyshop={bodyshop}
|
||||||
|
socket={socket}
|
||||||
|
job={job}
|
||||||
|
onSuccess={handleEarlyROSuccess}
|
||||||
|
showCancelButton={false}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name={["ins_co_nm"]}
|
||||||
|
label={t("jobs.fields.ins_co_nm")}
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
showSearch={{
|
||||||
|
optionFilterProp:'label'
|
||||||
|
}}
|
||||||
|
options={insuranceOptions}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{bodyshop.enforce_class && (
|
||||||
|
<Form.Item name="class" label={t("jobs.fields.class")} rules={[{ required: bodyshop.enforce_class }]}>
|
||||||
|
<Select options={classOptions} />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{bodyshop.enforce_referral && (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name="referral_source"
|
||||||
|
label={t("jobs.fields.referralsource")}
|
||||||
|
rules={[{ required: bodyshop.enforce_referral }]}
|
||||||
|
>
|
||||||
|
<Select options={referralOptions} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{bodyshop.enforce_conversion_csr && (
|
||||||
|
<Form.Item
|
||||||
|
name="employee_csr"
|
||||||
|
label={t(
|
||||||
|
InstanceRenderManager({
|
||||||
|
imex: "jobs.fields.employee_csr",
|
||||||
|
rome: "jobs.fields.employee_csr_writer"
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
rules={[{ required: bodyshop.enforce_conversion_csr }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
showSearch={{
|
||||||
|
optionFilterProp: 'label',
|
||||||
|
filterOption: (input, option) =>
|
||||||
|
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
|
||||||
|
}}
|
||||||
|
style={{ width: 200 }}
|
||||||
|
|
||||||
|
options={csrOptions}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{bodyshop.enforce_conversion_category && (
|
||||||
|
<Form.Item name="category" label={t("jobs.fields.category")} rules={[{ required: bodyshop.enforce_conversion_category }]}>
|
||||||
|
<Select allowClear options={categoryOptions} />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
|
||||||
|
<Form.Item label={t("jobs.fields.ca_gst_registrant")} name="ca_gst_registrant" valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item label={t("jobs.fields.driveable")} name="driveable" valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={t("jobs.fields.towin")} name="towin" valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Space wrap style={{ marginTop: 16 }}>
|
||||||
|
<Button
|
||||||
|
disabled={submitDisabled() || (isReynoldsMode && !job.dms_id && !earlyRoCreated)}
|
||||||
|
type="primary"
|
||||||
|
danger
|
||||||
|
onClick={() => form.submit()}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
{t("jobs.actions.convert")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button onClick={handleModalClose} disabled={earlyRoCreatedThisSession && !job.converted}>
|
||||||
|
{t("general.actions.close")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||||
<Select onChange={handleInsCoChange}>
|
<Select
|
||||||
{bodyshop.md_ins_cos.map((s) => (
|
onChange={handleInsCoChange}
|
||||||
<Select.Option key={s.name} value={s.name}>
|
options={bodyshop.md_ins_cos.map((s) => ({
|
||||||
{s.name}
|
value: s.name,
|
||||||
</Select.Option>
|
label: s.name
|
||||||
))}
|
}))}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
|
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
|
||||||
<Input />
|
<Input />
|
||||||
@@ -192,13 +192,12 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.referralsource")} name="referral_source">
|
<Form.Item label={t("jobs.fields.referralsource")} name="referral_source">
|
||||||
<Select>
|
<Select
|
||||||
{bodyshop.md_referral_sources.map((s) => (
|
options={bodyshop.md_referral_sources.map((s) => ({
|
||||||
<Select.Option key={s} value={s}>
|
value: s,
|
||||||
{s}
|
label: s
|
||||||
</Select.Option>
|
}))}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
||||||
<Input />
|
<Input />
|
||||||
@@ -221,10 +220,13 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<CurrencyInput min={0} />
|
<CurrencyInput min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
||||||
<Select allowClear>
|
<Select
|
||||||
<Select.Option value="W">{t("jobs.labels.deductible.waived")}</Select.Option>
|
allowClear
|
||||||
<Select.Option value="Y">{t("jobs.labels.deductible.stands")}</Select.Option>
|
options={[
|
||||||
</Select>
|
{ value: "W", label: t("jobs.labels.deductible.waived") },
|
||||||
|
{ value: "Y", label: t("jobs.labels.deductible.stands") }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.depreciation_taxes")} name="depreciation_taxes">
|
<Form.Item label={t("jobs.fields.depreciation_taxes")} name="depreciation_taxes">
|
||||||
<CurrencyInput />
|
<CurrencyInput />
|
||||||
|
|||||||
@@ -43,20 +43,19 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
||||||
<Select disabled={jobRO}>
|
<Select disabled={jobRO} options={[
|
||||||
<Select.Option value="W">{t("jobs.labels.deductible.waived")}</Select.Option>
|
{ value: "W", label: t("jobs.labels.deductible.waived") },
|
||||||
<Select.Option value="Y">{t("jobs.labels.deductible.stands")}</Select.Option>
|
{ value: "Y", label: t("jobs.labels.deductible.stands") }
|
||||||
</Select>
|
]} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
|
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
|
||||||
<CurrencyInput disabled={jobRO} min={0} />
|
<CurrencyInput disabled={jobRO} min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ded_note")} name="ded_note">
|
<Form.Item label={t("jobs.fields.ded_note")} name="ded_note">
|
||||||
<Select disabled={jobRO}>
|
<Select disabled={jobRO} options={bodyshop.md_ded_notes.map((n) => ({
|
||||||
{bodyshop.md_ded_notes.map((n, index) => (
|
value: n,
|
||||||
<Select.Option key={index}>{n}</Select.Option>
|
label: n
|
||||||
))}
|
}))} />
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
@@ -66,13 +65,10 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||||
<Select disabled={jobRO} onChange={handleInsCoChange}>
|
<Select disabled={jobRO} onChange={handleInsCoChange} options={bodyshop.md_ins_cos.map((s) => ({
|
||||||
{bodyshop.md_ins_cos.map((s) => (
|
value: s.name,
|
||||||
<Select.Option key={s.name} value={s.name}>
|
label: s.name
|
||||||
{s.name}
|
}))} />
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
|
<Form.Item label={t("jobs.fields.ins_addr1")} name="ins_addr1">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
@@ -123,25 +119,19 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select disabled={jobRO} allowClear>
|
<Select disabled={jobRO} allowClear options={bodyshop.md_referral_sources.map((s) => ({
|
||||||
{bodyshop.md_referral_sources.map((s) => (
|
value: s,
|
||||||
<Select.Option key={s} value={s}>
|
label: s
|
||||||
{s}
|
}))} />
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
|
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
|
||||||
<Select disabled={jobRO} allowClear>
|
<Select disabled={jobRO} allowClear options={bodyshop.appt_alt_transport.map((s) => ({
|
||||||
{bodyshop.appt_alt_transport.map((s) => (
|
value: s,
|
||||||
<Select.Option key={s} value={s}>
|
label: s
|
||||||
{s}
|
}))} />
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
@@ -243,15 +233,11 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow header={t("jobs.forms.other")}>
|
<FormRow header={t("jobs.forms.other")}>
|
||||||
<Form.Item label={t("jobs.fields.category")} name="category">
|
<Form.Item label={t("jobs.fields.category")} name="category">
|
||||||
<Select disabled={jobRO} allowClear>
|
<Select disabled={jobRO} allowClear options={bodyshop.md_categories.map((s) => ({
|
||||||
{bodyshop.md_categories.map((s) => (
|
value: s,
|
||||||
<Select.Option key={s} value={s}>
|
label: s
|
||||||
{s}
|
}))} />
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("jobs.fields.selling_dealer")} name="selling_dealer">
|
<Form.Item label={t("jobs.fields.selling_dealer")} name="selling_dealer">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -267,6 +253,21 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
<Form.Item label={t("jobs.fields.lost_sale_reason")} name="lost_sale_reason">
|
<Form.Item label={t("jobs.fields.lost_sale_reason")} name="lost_sale_reason">
|
||||||
<Input disabled={jobRO} allowClear />
|
<Input disabled={jobRO} allowClear />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
{bodyshop.rr_dealerid && (
|
||||||
|
<Form.Item label={t("jobs.fields.dms.id")} name="dms_id">
|
||||||
|
<Input disabled />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{bodyshop.rr_dealerid && (
|
||||||
|
<Form.Item label={t("jobs.fields.dms.advisor")} name="dms_advisor_id">
|
||||||
|
<Input disabled />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{bodyshop.rr_dealerid && (
|
||||||
|
<Form.Item label={t("jobs.fields.dms.customer")} name="dms_customer_id">
|
||||||
|
<Input disabled />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
</FormRow>
|
</FormRow>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -157,7 +157,6 @@ export function JobsDetailHeaderActions({
|
|||||||
variables: watcherVars,
|
variables: watcherVars,
|
||||||
skip: !jobId,
|
skip: !jobId,
|
||||||
fetchPolicy: "cache-first",
|
fetchPolicy: "cache-first",
|
||||||
notifyOnNetworkStatusChange: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const jobWatchersCount = jobWatchersData?.job_watchers?.length ?? job?.job_watchers?.length ?? 0;
|
const jobWatchersCount = jobWatchersData?.job_watchers?.length ?? job?.job_watchers?.length ?? 0;
|
||||||
@@ -715,13 +714,12 @@ export function JobsDetailHeaderActions({
|
|||||||
<FormDateTimePickerComponent />
|
<FormDateTimePickerComponent />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("appointments.fields.color")} name="color">
|
<Form.Item label={t("appointments.fields.color")} name="color">
|
||||||
<Select>
|
<Select
|
||||||
{bodyshop.appt_colors.map((col, idx) => (
|
options={bodyshop.appt_colors.map((col) => ({
|
||||||
<Select.Option key={idx} value={col.color.hex}>
|
value: col.color.hex,
|
||||||
{col.label}
|
label: col.label
|
||||||
</Select.Option>
|
}))}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|||||||
@@ -94,22 +94,26 @@ export function LaborAllocationsAdjustmentEdit({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select allowClear disabled={!!mod_lbr_ty}>
|
<Select
|
||||||
<Select.Option value="LAA">{t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
allowClear
|
||||||
<Select.Option value="LAB">{t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
disabled={!!mod_lbr_ty}
|
||||||
<Select.Option value="LAD">{t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
options={[
|
||||||
<Select.Option value="LAE">{t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
{ value: "LAA", label: t("joblines.fields.lbr_types.LAA") },
|
||||||
<Select.Option value="LAF">{t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
{ value: "LAB", label: t("joblines.fields.lbr_types.LAB") },
|
||||||
<Select.Option value="LAG">{t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
{ value: "LAD", label: t("joblines.fields.lbr_types.LAD") },
|
||||||
<Select.Option value="LAM">{t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
{ value: "LAE", label: t("joblines.fields.lbr_types.LAE") },
|
||||||
<Select.Option value="LAR">{t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
{ value: "LAF", label: t("joblines.fields.lbr_types.LAF") },
|
||||||
<Select.Option value="LAS">{t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
{ value: "LAG", label: t("joblines.fields.lbr_types.LAG") },
|
||||||
<Select.Option value="LAU">{t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
{ value: "LAM", label: t("joblines.fields.lbr_types.LAM") },
|
||||||
<Select.Option value="LA1">{t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
{ value: "LAR", label: t("joblines.fields.lbr_types.LAR") },
|
||||||
<Select.Option value="LA2">{t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
{ value: "LAS", label: t("joblines.fields.lbr_types.LAS") },
|
||||||
<Select.Option value="LA3">{t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
{ value: "LAU", label: t("joblines.fields.lbr_types.LAU") },
|
||||||
<Select.Option value="LA4">{t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
{ value: "LA1", label: t("joblines.fields.lbr_types.LA1") },
|
||||||
</Select>
|
{ value: "LA2", label: t("joblines.fields.lbr_types.LA2") },
|
||||||
|
{ value: "LA3", label: t("joblines.fields.lbr_types.LA3") },
|
||||||
|
{ value: "LA4", label: t("joblines.fields.lbr_types.LA4") }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("jobs.fields.adjustmenthours")}
|
label={t("jobs.fields.adjustmenthours")}
|
||||||
@@ -132,7 +136,13 @@ export function LaborAllocationsAdjustmentEdit({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={(vis) => setOpen(vis)} content={overlay} trigger="click">
|
<Popover
|
||||||
|
getPopupContainer={(trigger) => trigger?.parentElement || document.body}
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(vis) => setOpen(vis)}
|
||||||
|
content={overlay}
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount,
|
|||||||
where: whereClause
|
where: whereClause
|
||||||
},
|
},
|
||||||
fetchPolicy: "cache-and-network",
|
fetchPolicy: "cache-and-network",
|
||||||
notifyOnNetworkStatusChange: true,
|
|
||||||
errorPolicy: "all",
|
errorPolicy: "all",
|
||||||
pollInterval: isConnected ? 0 : day.duration(NOTIFICATION_POLL_INTERVAL_SECONDS, "seconds").asMilliseconds(),
|
pollInterval: isConnected ? 0 : day.duration(NOTIFICATION_POLL_INTERVAL_SECONDS, "seconds").asMilliseconds(),
|
||||||
skip: skipQuery
|
skip: skipQuery
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { store } from "../../redux/store";
|
import { store } from "../../redux/store";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { Tooltip } from "antd";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -11,15 +12,27 @@ const mapDispatchToProps = () => ({
|
|||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(OwnerNameDisplay);
|
export default connect(mapStateToProps, mapDispatchToProps)(OwnerNameDisplay);
|
||||||
|
|
||||||
export function OwnerNameDisplay({ bodyshop, ownerObject }) {
|
export function OwnerNameDisplay({ bodyshop, ownerObject, withToolTip = false }) {
|
||||||
const emptyTest = ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
|
const emptyTest = ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
|
||||||
|
|
||||||
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "") return "N/A";
|
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "") return "N/A";
|
||||||
|
|
||||||
if (bodyshop.last_name_first)
|
let returnString;
|
||||||
return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_co_nm || ""}`.trim();
|
if (bodyshop.last_name_first) {
|
||||||
|
returnString =
|
||||||
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${ownerObject.ownr_co_nm || ""}`.trim();
|
`${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_co_nm || ""}`.trim();
|
||||||
|
} else {
|
||||||
|
returnString = `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${ownerObject.ownr_co_nm || ""}`.trim();
|
||||||
|
}
|
||||||
|
if (withToolTip) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={returnString} mouseEnterDelay={0.5}>
|
||||||
|
{returnString}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return returnString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import { SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_OWNERS_FOR_AUTOCOMPLETE }
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const OwnerSearchSelect = ({ value, onChange, onBlur, disabled, ref }) => {
|
const OwnerSearchSelect = ({ value, onChange, onBlur, disabled, ref }) => {
|
||||||
const [callSearch, { loading, error, data }] = useLazyQuery(SEARCH_OWNERS_FOR_AUTOCOMPLETE);
|
const [callSearch, { loading, error, data }] = useLazyQuery(SEARCH_OWNERS_FOR_AUTOCOMPLETE);
|
||||||
|
|
||||||
@@ -16,9 +14,10 @@ const OwnerSearchSelect = ({ value, onChange, onBlur, disabled, ref }) => {
|
|||||||
SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE
|
SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE
|
||||||
);
|
);
|
||||||
|
|
||||||
const executeSearch = (v) => {
|
const executeSearch = (variables) => {
|
||||||
if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch({ variables: v.variables });
|
if (variables?.search !== "" && variables?.search?.length >= 2) callSearch({ variables });
|
||||||
};
|
};
|
||||||
|
|
||||||
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
||||||
|
|
||||||
const handleSearch = (value) => {
|
const handleSearch = (value) => {
|
||||||
@@ -70,15 +69,12 @@ const OwnerSearchSelect = ({ value, onChange, onBlur, disabled, ref }) => {
|
|||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
>
|
options={theOptions?.map((o) => ({
|
||||||
{theOptions
|
key: o.id,
|
||||||
? theOptions.map((o) => (
|
value: o.id,
|
||||||
<Option key={o.id} value={o.id}>
|
label: `${OwnerNameDisplayFunction(o)} | ${o.ownr_addr1 || ""} `
|
||||||
{`${OwnerNameDisplayFunction(o)} | ${o.ownr_addr1 || ""} `}
|
}))}
|
||||||
</Option>
|
/>
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</Select>
|
|
||||||
{idLoading || loading ? <LoadingOutlined /> : null}
|
{idLoading || loading ? <LoadingOutlined /> : null}
|
||||||
{error ? <AlertComponent title={error.message} type="error" /> : null}
|
{error ? <AlertComponent title={error.message} type="error" /> : null}
|
||||||
{idError ? <AlertComponent title={idError.message} type="error" /> : null}
|
{idError ? <AlertComponent title={idError.message} type="error" /> : null}
|
||||||
|
|||||||
@@ -1,94 +1,121 @@
|
|||||||
import { DownOutlined } from "@ant-design/icons";
|
import { DownOutlined } from "@ant-design/icons";
|
||||||
import { Dropdown, InputNumber, Space } from "antd";
|
import { Button, Divider, Dropdown, InputNumber, Space, theme } from "antd";
|
||||||
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
|
||||||
|
const DISCOUNT_PRESETS = [5, 10, 15, 20, 25, 40];
|
||||||
|
|
||||||
export default function PartsOrderModalPriceChange({ form, field }) {
|
export default function PartsOrderModalPriceChange({ form, field }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const menu = {
|
const { token } = theme.useToken();
|
||||||
items: [
|
|
||||||
{
|
|
||||||
key: "5",
|
|
||||||
label: t("parts_orders.labels.discount", { percent: "5%" })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "10",
|
|
||||||
label: t("parts_orders.labels.discount", { percent: "10%" })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "15",
|
|
||||||
label: t("parts_orders.labels.discount", { percent: "15%" })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "20",
|
|
||||||
label: t("parts_orders.labels.discount", { percent: "20%" })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "25",
|
|
||||||
label: t("parts_orders.labels.discount", { percent: "25%" })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "40",
|
|
||||||
label: t("parts_orders.labels.discount", { percent: "40%" })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "custom",
|
|
||||||
label: (
|
|
||||||
<Space.Compact>
|
|
||||||
<InputNumber
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
onKeyUp={(e) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
const values = form.getFieldsValue();
|
|
||||||
const { parts_order_lines } = values;
|
|
||||||
|
|
||||||
form.setFieldsValue({
|
const [open, setOpen] = useState(false);
|
||||||
parts_order_lines: {
|
const [customPercent, setCustomPercent] = useState(0);
|
||||||
data: parts_order_lines.data.map((p, idx) => {
|
|
||||||
if (idx !== field.name) return p;
|
const applyDiscountPercent = (percent) => {
|
||||||
console.log(p, e.target.value, (p.act_price || 0) * ((100 - (e.target.value || 0)) / 100));
|
const pct = Number(percent) || 0;
|
||||||
return {
|
|
||||||
...p,
|
const values = form.getFieldsValue();
|
||||||
act_price: (p.act_price || 0) * ((100 - (e.target.value || 0)) / 100)
|
const parts_order_lines = values?.parts_order_lines;
|
||||||
};
|
const data = Array.isArray(parts_order_lines?.data) ? parts_order_lines.data : [];
|
||||||
})
|
if (!data.length) return;
|
||||||
}
|
|
||||||
});
|
form.setFieldsValue({
|
||||||
e.target.value = 0;
|
parts_order_lines: {
|
||||||
}
|
data: data.map((p, idx) => {
|
||||||
}}
|
if (idx !== field.name) return p;
|
||||||
min={0}
|
return {
|
||||||
max={100}
|
...p,
|
||||||
/>
|
act_price: (p.act_price || 0) * ((100 - pct) / 100)
|
||||||
<span style={{ padding: "0 11px", backgroundColor: "#fafafa", border: "1px solid #d9d9d9", borderLeft: 0 }}>%</span>
|
};
|
||||||
</Space.Compact>
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
],
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyCustom = () => {
|
||||||
|
logImEXEvent("parts_order_manual_discount", {});
|
||||||
|
applyDiscountPercent(customPercent);
|
||||||
|
setCustomPercent(0);
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const menu = {
|
||||||
|
// Kill the menu “card” styling so our wrapper becomes the single card.
|
||||||
|
style: {
|
||||||
|
background: "transparent",
|
||||||
|
boxShadow: "none"
|
||||||
|
},
|
||||||
|
items: DISCOUNT_PRESETS.map((pct) => ({
|
||||||
|
key: String(pct),
|
||||||
|
label: t("parts_orders.labels.discount", { percent: `${pct}%` })
|
||||||
|
})),
|
||||||
onClick: ({ key }) => {
|
onClick: ({ key }) => {
|
||||||
logImEXEvent("parts_order_manual_discount", {});
|
logImEXEvent("parts_order_manual_discount", {});
|
||||||
if (key === "custom") return;
|
applyDiscountPercent(key);
|
||||||
const values = form.getFieldsValue();
|
setOpen(false);
|
||||||
const { parts_order_lines } = values;
|
|
||||||
form.setFieldsValue({
|
|
||||||
parts_order_lines: {
|
|
||||||
data: parts_order_lines.data.map((p, idx) => {
|
|
||||||
if (idx !== field.name) return p;
|
|
||||||
return {
|
|
||||||
...p,
|
|
||||||
act_price: (p.act_price || 0) * ((100 - key) / 100)
|
|
||||||
};
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown menu={menu} trigger="click">
|
<Dropdown
|
||||||
|
menu={menu}
|
||||||
|
trigger={["click"]}
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(nextOpen) => setOpen(nextOpen)}
|
||||||
|
getPopupContainer={(triggerNode) => triggerNode?.parentElement ?? document.body}
|
||||||
|
popupRender={(menus) => (
|
||||||
|
<div
|
||||||
|
// This makes the whole dropdown (menu + footer) look like one panel in both light/dark.
|
||||||
|
style={{
|
||||||
|
background: token.colorBgElevated,
|
||||||
|
borderRadius: token.borderRadiusLG,
|
||||||
|
boxShadow: token.boxShadowSecondary,
|
||||||
|
overflow: "hidden",
|
||||||
|
minWidth: 180
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{menus}
|
||||||
|
|
||||||
|
<Divider style={{ margin: 0 }} />
|
||||||
|
|
||||||
|
<div style={{ padding: token.paddingXS }}>
|
||||||
|
<Space.Compact style={{ width: "100%" }}>
|
||||||
|
<InputNumber
|
||||||
|
value={customPercent}
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
precision={0}
|
||||||
|
controls={false}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
formatter={(v) => (v === null || v === undefined ? "" : `${v}%`)}
|
||||||
|
parser={(v) =>
|
||||||
|
String(v ?? "")
|
||||||
|
.replace("%", "")
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
onChange={(v) => setCustomPercent(v ?? 0)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
applyCustom();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type="primary" onClick={applyCustom}>
|
||||||
|
{t("general.labels.apply")}
|
||||||
|
</Button>
|
||||||
|
</Space.Compact>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Space>
|
<Space>
|
||||||
%
|
% <DownOutlined />
|
||||||
<DownOutlined />
|
|
||||||
</Space>
|
</Space>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -158,19 +158,21 @@ export function PartsOrderModalComponent({
|
|||||||
key={`${index}part_type`}
|
key={`${index}part_type`}
|
||||||
name={[field.name, "part_type"]}
|
name={[field.name, "part_type"]}
|
||||||
>
|
>
|
||||||
<Select disabled={!(sendType === "oec" && OEConnection_PriceChange.treatment === "on")}>
|
<Select
|
||||||
<Select.Option value="PAA">{t("joblines.fields.part_types.PAA")}</Select.Option>
|
disabled={!(sendType === "oec" && OEConnection_PriceChange.treatment === "on")}
|
||||||
<Select.Option value="PAC">{t("joblines.fields.part_types.PAC")}</Select.Option>
|
options={[
|
||||||
|
{ value: "PAA", label: t("joblines.fields.part_types.PAA") },
|
||||||
<Select.Option value="PAL">{t("joblines.fields.part_types.PAL")}</Select.Option>
|
{ value: "PAC", label: t("joblines.fields.part_types.PAC") },
|
||||||
<Select.Option value="PAG">{t("joblines.fields.part_types.PAG")}</Select.Option>
|
{ value: "PAL", label: t("joblines.fields.part_types.PAL") },
|
||||||
<Select.Option value="PAM">{t("joblines.fields.part_types.PAM")}</Select.Option>
|
{ value: "PAG", label: t("joblines.fields.part_types.PAG") },
|
||||||
<Select.Option value="PAP">{t("joblines.fields.part_types.PAP")}</Select.Option>
|
{ value: "PAM", label: t("joblines.fields.part_types.PAM") },
|
||||||
<Select.Option value="PAN">{t("joblines.fields.part_types.PAN")}</Select.Option>
|
{ value: "PAP", label: t("joblines.fields.part_types.PAP") },
|
||||||
<Select.Option value="PAO">{t("joblines.fields.part_types.PAO")}</Select.Option>
|
{ value: "PAN", label: t("joblines.fields.part_types.PAN") },
|
||||||
<Select.Option value="PAR">{t("joblines.fields.part_types.PAR")}</Select.Option>
|
{ value: "PAO", label: t("joblines.fields.part_types.PAO") },
|
||||||
<Select.Option value="PAS">{t("joblines.fields.part_types.PAS")}</Select.Option>
|
{ value: "PAR", label: t("joblines.fields.part_types.PAR") },
|
||||||
</Select>
|
{ value: "PAS", label: t("joblines.fields.part_types.PAS") }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("parts_orders.fields.oem_partno")}
|
label={t("parts_orders.fields.oem_partno")}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
|
||||||
import PartsOrderModalComponent from "./parts-order-modal.component";
|
import PartsOrderModalComponent from "./parts-order-modal.component";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
||||||
@@ -66,7 +65,7 @@ export function PartsOrderModalContainer({
|
|||||||
const sendTypeState = useState("e");
|
const sendTypeState = useState("e");
|
||||||
const sendType = sendTypeState[0];
|
const sendType = sendTypeState[0];
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
|
const { error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
|
||||||
skip: !open,
|
skip: !open,
|
||||||
variables: { jobId: jobId },
|
variables: { jobId: jobId },
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
@@ -94,16 +93,6 @@ export function PartsOrderModalContainer({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const missingIdx = forcedLines.findIndex((l) => !l?.job_line_id);
|
|
||||||
if (missingIdx !== -1) {
|
|
||||||
notification.error({
|
|
||||||
title: t("parts_orders.errors.creating"),
|
|
||||||
description: `Missing job_line_id for parts line #${missingIdx + 1}`
|
|
||||||
});
|
|
||||||
setSaving(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let insertResult;
|
let insertResult;
|
||||||
try {
|
try {
|
||||||
insertResult = await insertPartOrder({
|
insertResult = await insertPartOrder({
|
||||||
@@ -372,6 +361,7 @@ export function PartsOrderModalContainer({
|
|||||||
}
|
}
|
||||||
}, [open, linesToOrder, form]);
|
}, [open, linesToOrder, form]);
|
||||||
|
|
||||||
|
//This used to have a loading component spinner for the vendor data. With Apollo 4, the NetworkState isn't emitting correctly, so loading just gets set to true the second time, and no longer works as expected.
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
@@ -390,18 +380,14 @@ export function PartsOrderModalContainer({
|
|||||||
>
|
>
|
||||||
{error ? <AlertComponent title={error.message} type="error" /> : null}
|
{error ? <AlertComponent title={error.message} type="error" /> : null}
|
||||||
<Form form={form} layout="vertical" autoComplete="no" onFinish={handleFinish} initialValues={initialValues}>
|
<Form form={form} layout="vertical" autoComplete="no" onFinish={handleFinish} initialValues={initialValues}>
|
||||||
{loading ? (
|
<PartsOrderModalComponent
|
||||||
<LoadingSpinner />
|
form={form}
|
||||||
) : (
|
vendorList={data?.vendors || []}
|
||||||
<PartsOrderModalComponent
|
sendTypeState={sendTypeState}
|
||||||
form={form}
|
isReturn={isReturn}
|
||||||
vendorList={data?.vendors || []}
|
preferredMake={data && data.jobs[0] && data.jobs[0].v_make_desc}
|
||||||
sendTypeState={sendTypeState}
|
job={job}
|
||||||
isReturn={isReturn}
|
/>
|
||||||
preferredMake={data && data.jobs[0] && data.jobs[0].v_make_desc}
|
|
||||||
job={job}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { useQuery } from "@apollo/client/react";
|
import { useQuery } from "@apollo/client/react";
|
||||||
import { Button, Card, Input, Space, Table } from "antd";
|
import { Button, Card, Checkbox, Input, Space, Table } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -31,6 +31,8 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
const { selected, sortcolumn, sortorder, statusFilters } = searchParams;
|
const { selected, sortcolumn, sortorder, statusFilters } = searchParams;
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const [filter, setFilter] = useLocalStorage("filter_parts_queue", null);
|
const [filter, setFilter] = useLocalStorage("filter_parts_queue", null);
|
||||||
|
const [viewTimeStamp, setViewTimeStamp] = useLocalStorage("parts_queue_timestamps", false);
|
||||||
|
const [countsOnly, setCountsOnly] = useLocalStorage("parts_queue_counts_only", false);
|
||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(QUERY_PARTS_QUEUE, {
|
const { loading, error, data, refetch } = useQuery(QUERY_PARTS_QUEUE, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
@@ -92,6 +94,7 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
dataIndex: "ro_number",
|
dataIndex: "ro_number",
|
||||||
key: "ro_number",
|
key: "ro_number",
|
||||||
|
width: "110px",
|
||||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||||
sortOrder: sortcolumn === "ro_number" && sortorder,
|
sortOrder: sortcolumn === "ro_number" && sortorder,
|
||||||
|
|
||||||
@@ -103,16 +106,20 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
title: t("jobs.fields.owner"),
|
title: t("jobs.fields.owner"),
|
||||||
dataIndex: "ownr_ln",
|
dataIndex: "ownr_ln",
|
||||||
key: "ownr_ln",
|
key: "ownr_ln",
|
||||||
|
width: "8%",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: true
|
||||||
|
},
|
||||||
sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||||
sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.ownerid ? (
|
return record.ownerid ? (
|
||||||
<Link to={"/manage/owners/" + record.ownerid}>
|
<Link to={"/manage/owners/" + record.ownerid}>
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
<OwnerNameDisplay ownerObject={record} withToolTip />
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
<OwnerNameDisplay ownerObject={record} withToolTip />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -187,7 +194,7 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: (a, b) => dateSort(a.scheduled_in, b.scheduled_in),
|
sorter: (a, b) => dateSort(a.scheduled_in, b.scheduled_in),
|
||||||
sortOrder: sortcolumn === "scheduled_in" && sortorder,
|
sortOrder: sortcolumn === "scheduled_in" && sortorder,
|
||||||
render: (text, record) => <DateTimeFormatter>{record.scheduled_in}</DateTimeFormatter>
|
render: (text, record) => <DateTimeFormatter hideTime={!viewTimeStamp}>{record.scheduled_in}</DateTimeFormatter>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.scheduled_completion"),
|
title: t("jobs.fields.scheduled_completion"),
|
||||||
@@ -196,7 +203,9 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion),
|
sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion),
|
||||||
sortOrder: sortcolumn === "scheduled_completion" && sortorder,
|
sortOrder: sortcolumn === "scheduled_completion" && sortorder,
|
||||||
render: (text, record) => <DateTimeFormatter>{record.scheduled_completion}</DateTimeFormatter>
|
render: (text, record) => (
|
||||||
|
<DateTimeFormatter hideTime={!viewTimeStamp}>{record.scheduled_completion}</DateTimeFormatter>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// title: t("vehicles.fields.plate_no"),
|
// title: t("vehicles.fields.plate_no"),
|
||||||
@@ -227,16 +236,23 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
title: t("jobs.fields.updated_at"),
|
title: t("jobs.fields.updated_at"),
|
||||||
dataIndex: "updated_at",
|
dataIndex: "updated_at",
|
||||||
key: "updated_at",
|
key: "updated_at",
|
||||||
|
width: "110px",
|
||||||
sorter: (a, b) => dateSort(a.updated_at, b.updated_at),
|
sorter: (a, b) => dateSort(a.updated_at, b.updated_at),
|
||||||
sortOrder: sortcolumn === "updated_at" && sortorder,
|
sortOrder: sortcolumn === "updated_at" && sortorder,
|
||||||
render: (text, record) => <TimeAgoFormatter>{record.updated_at}</TimeAgoFormatter>
|
render: (text, record) => <TimeAgoFormatter removeAgoString>{record.updated_at}</TimeAgoFormatter>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.partsstatus"),
|
title: t("jobs.fields.partsstatus"),
|
||||||
dataIndex: "partsstatus",
|
dataIndex: "partsstatus",
|
||||||
key: "partsstatus",
|
key: "partsstatus",
|
||||||
|
width: countsOnly ? "180px" : "110px",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<JobPartsReceived parts={record.joblines_status} displayMode="full" popoverPlacement="topLeft" />
|
<JobPartsReceived
|
||||||
|
parts={record.joblines_status}
|
||||||
|
displayMode="full"
|
||||||
|
popoverPlacement="middle"
|
||||||
|
countsOnly={countsOnly}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -249,6 +265,7 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
title: t("jobs.fields.queued_for_parts"),
|
title: t("jobs.fields.queued_for_parts"),
|
||||||
dataIndex: "queued_for_parts",
|
dataIndex: "queued_for_parts",
|
||||||
key: "queued_for_parts",
|
key: "queued_for_parts",
|
||||||
|
width: "120px",
|
||||||
sorter: (a, b) => a.queued_for_parts - b.queued_for_parts,
|
sorter: (a, b) => a.queued_for_parts - b.queued_for_parts,
|
||||||
sortOrder: sortcolumn === "queued_for_parts" && sortorder,
|
sortOrder: sortcolumn === "queued_for_parts" && sortorder,
|
||||||
filteredValue: filter?.queued_for_parts || null,
|
filteredValue: filter?.queued_for_parts || null,
|
||||||
@@ -275,6 +292,12 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
<Button onClick={() => refetch()}>
|
<Button onClick={() => refetch()}>
|
||||||
<SyncOutlined />
|
<SyncOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Checkbox checked={countsOnly} onChange={(e) => setCountsOnly(e.target.checked)}>
|
||||||
|
{t("parts.labels.view_counts_only")}
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox checked={viewTimeStamp} onChange={(e) => setViewTimeStamp(e.target.checked)}>
|
||||||
|
{t("parts.labels.view_timestamps")}
|
||||||
|
</Checkbox>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
className="imex-table-header__search"
|
className="imex-table-header__search"
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
@@ -299,7 +322,7 @@ export function PartsQueueListComponent({ bodyshop }) {
|
|||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={jobs}
|
dataSource={jobs}
|
||||||
style={{ height: "100%" }}
|
style={{ height: "100%" }}
|
||||||
scroll={{ x: true }}
|
//scroll={{ x: true }}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
onSelect: (record) => {
|
onSelect: (record) => {
|
||||||
|
|||||||
@@ -29,13 +29,12 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
options={bodyshop.md_parts_locations.map((loc, idx) => ({
|
||||||
{bodyshop.md_parts_locations.map((loc, idx) => (
|
key: idx,
|
||||||
<Select.Option key={idx} value={loc}>
|
value: loc,
|
||||||
{loc}
|
label: loc
|
||||||
</Select.Option>
|
}))}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Typography.Title level={4}>{t("parts_orders.labels.inthisorder")}</Typography.Title>
|
<Typography.Title level={4}>{t("parts_orders.labels.inthisorder")}</Typography.Title>
|
||||||
@@ -85,13 +84,14 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
|
|||||||
key={`${index}location`}
|
key={`${index}location`}
|
||||||
name={[field.name, "location"]}
|
name={[field.name, "location"]}
|
||||||
>
|
>
|
||||||
<Select style={{ width: "10rem" }}>
|
<Select
|
||||||
{bodyshop.md_parts_locations.map((loc, idx) => (
|
style={{ width: "10rem" }}
|
||||||
<Select.Option key={idx} value={loc}>
|
options={bodyshop.md_parts_locations.map((loc, idx) => ({
|
||||||
{loc}
|
key: idx,
|
||||||
</Select.Option>
|
value: loc,
|
||||||
))}
|
label: loc
|
||||||
</Select>
|
}))}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("parts_orders.fields.quantity")}
|
label={t("parts_orders.fields.quantity")}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { Button, Card, Divider, Form, Input, Select, Space } from "antd";
|
|||||||
import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
|
import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
export default function PartsShopInfoEmailPresets() {
|
export default function PartsShopInfoEmailPresets() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -26,13 +24,7 @@ export default function PartsShopInfoEmailPresets() {
|
|||||||
label={t("bodyshop.labels.email_type")}
|
label={t("bodyshop.labels.email_type")}
|
||||||
rules={[{ required: true, message: t("bodyshop.errors.email_type_required") }]}
|
rules={[{ required: true, message: t("bodyshop.errors.email_type_required") }]}
|
||||||
>
|
>
|
||||||
<Select placeholder={t("bodyshop.placeholders.select_email_type")}>
|
<Select placeholder={t("bodyshop.placeholders.select_email_type")} options={emailTypes} />
|
||||||
{emailTypes.map((type) => (
|
|
||||||
<Option key={type.value} value={type.value}>
|
|
||||||
{type.label}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
{...restField}
|
{...restField}
|
||||||
|
|||||||
@@ -91,20 +91,25 @@ export function PaymentFormComponent({ form, bodyshop, disabled }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select disabled={disabled}>
|
<Select disabled={disabled}
|
||||||
<Select.Option value={t("payments.labels.customer")}>{t("payments.labels.customer")}</Select.Option>
|
options={Qb_Multi_Ar.treatment === "on"
|
||||||
{Qb_Multi_Ar.treatment === "on" ? (
|
? [
|
||||||
<Select.OptGroup label={t("payments.labels.external")}>
|
{ value: t("payments.labels.customer"), label: t("payments.labels.customer") },
|
||||||
{bodyshop.md_ins_cos.map((i, idx) => (
|
{
|
||||||
<Select.Option key={idx} value={i.name}>
|
label: t("payments.labels.external"),
|
||||||
{i.name}
|
options: bodyshop.md_ins_cos.map((i, idx) => ({
|
||||||
</Select.Option>
|
key: idx,
|
||||||
))}
|
value: i.name,
|
||||||
</Select.OptGroup>
|
label: i.name
|
||||||
) : (
|
}))
|
||||||
<Select.Option value={t("payments.labels.insurance")}>{t("payments.labels.insurance")}</Select.Option>
|
}
|
||||||
)}
|
]
|
||||||
</Select>
|
: [
|
||||||
|
{ value: t("payments.labels.customer"), label: t("payments.labels.customer") },
|
||||||
|
{ value: t("payments.labels.insurance"), label: t("payments.labels.insurance") }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -117,13 +122,13 @@ export function PaymentFormComponent({ form, bodyshop, disabled }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select disabled={disabled}>
|
<Select disabled={disabled}
|
||||||
{bodyshop.md_payment_types.map((v, idx) => (
|
options={bodyshop.md_payment_types.map((v, idx) => ({
|
||||||
<Select.Option key={idx} value={v}>
|
key: idx,
|
||||||
{v}
|
value: v,
|
||||||
</Select.Option>
|
label: v
|
||||||
))}
|
}))}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default function PaymentFormTotalPayments({ jobid }) {
|
|||||||
{balance && (
|
{balance && (
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("payments.labels.balance")}
|
title={t("payments.labels.balance")}
|
||||||
styles={{ value: { color: balance.getAmount() !== 0 ? "red" : "green" } }}
|
styles={{ content: { color: balance.getAmount() !== 0 ? "red" : "green" } }}
|
||||||
value={(balance && balance.toFormat()) || ""}
|
value={(balance && balance.toFormat()) || ""}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export function PrintCenterJobsLabels({ jobId }) {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Popover content={content} open={isModalVisible}>
|
<Popover content={content} open={isModalVisible} getPopupContainer={(trigger) => trigger.parentElement}>
|
||||||
<Button onClick={() => setIsModalVisible(true)}>{t("printcenter.jobs.labels.labels")}</Button>
|
<Button onClick={() => setIsModalVisible(true)}>{t("printcenter.jobs.labels.labels")}</Button>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
|
|||||||
|
|
||||||
const result = await updateJob({
|
const result = await updateJob({
|
||||||
variables: { jobId: record.id, job: { [empAssignment]: employeeid } }
|
variables: { jobId: record.id, job: { [empAssignment]: employeeid } }
|
||||||
|
|
||||||
// awaitRefetchQueries: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
@@ -55,6 +53,7 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
|
|||||||
|
|
||||||
await refetch();
|
await refetch();
|
||||||
|
|
||||||
|
setAssignment({ operation: null, employeeid: null });
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
const handleRemove = async (operation) => {
|
const handleRemove = async (operation) => {
|
||||||
@@ -84,6 +83,7 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
|
|||||||
|
|
||||||
await refetch();
|
await refetch();
|
||||||
|
|
||||||
|
setAssignment({ operation: null, employeeid: null });
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -94,29 +94,31 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
|
|||||||
|
|
||||||
const [visibility, setVisibility] = useState(false);
|
const [visibility, setVisibility] = useState(false);
|
||||||
const onChange = (e, option) => {
|
const onChange = (e, option) => {
|
||||||
setAssignment({ ...assignment, employeeid: e, name: option.name });
|
setAssignment({ ...assignment, employeeid: e, name: option.label });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const employeeOptions = bodyshop.employees
|
||||||
|
.filter((emp) => emp.active)
|
||||||
|
.map((emp) => ({
|
||||||
|
value: emp.id,
|
||||||
|
label: `${emp.first_name} ${emp.last_name}`,
|
||||||
|
name: `${emp.first_name} ${emp.last_name}`
|
||||||
|
}));
|
||||||
|
|
||||||
const popContent = (
|
const popContent = (
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Select
|
<Select
|
||||||
id="employeeSelector"
|
id="employeeSelector"
|
||||||
showSearch={{
|
showSearch={{
|
||||||
optionFilterProp: "children",
|
optionFilterProp: "label",
|
||||||
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
filterOption: (input, option) => option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
}}
|
}}
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
|
value={assignment.employeeid}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
>
|
options={employeeOptions}
|
||||||
{bodyshop.employees
|
/>
|
||||||
.filter((emp) => emp.active)
|
|
||||||
.map((emp) => (
|
|
||||||
<Select.Option value={emp.id} key={emp.id} name={`${emp.first_name} ${emp.last_name}`}>
|
|
||||||
{`${emp.first_name} ${emp.last_name}`}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
@@ -141,25 +143,25 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
|
|||||||
if (record[type]) theEmployee = bodyshop.employees.find((e) => e.id === record[type]);
|
if (record[type]) theEmployee = bodyshop.employees.find((e) => e.id === record[type]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover destroyOnHidden content={popContent} open={visibility}>
|
<Spin spinning={loading}>
|
||||||
<Spin spinning={loading}>
|
{record[type] ? (
|
||||||
{record[type] ? (
|
<div style={{ cursor: "pointer" }}>
|
||||||
<div style={{ cursor: "pointer" }}>
|
<span>{`${theEmployee?.first_name || ""} ${theEmployee?.last_name || ""}`}</span>
|
||||||
<span>{`${theEmployee?.first_name || ""} ${theEmployee?.last_name || ""}`}</span>
|
<DeleteFilled style={iconStyle} onClick={() => handleRemove(type)} />
|
||||||
<DeleteFilled style={iconStyle} onClick={() => handleRemove(type)} />
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<Popover destroyOnHidden content={popContent} open={visibility} trigger="click">
|
||||||
<PlusCircleFilled
|
<PlusCircleFilled
|
||||||
style={{ ...iconStyle, cursor: "pointer" }}
|
style={{ ...iconStyle, cursor: "pointer" }}
|
||||||
className="muted-button"
|
className="muted-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAssignment({ operation: type });
|
setAssignment({ operation: type, employeeid: null });
|
||||||
setVisibility(true);
|
setVisibility(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
</Popover>
|
||||||
</Spin>
|
)}
|
||||||
</Popover>
|
</Spin>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -453,10 +453,10 @@ export function ProductionListConfigManager({
|
|||||||
}}
|
}}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
placeholder={t("production.labels.selectview")}
|
placeholder={t("production.labels.selectview")}
|
||||||
optionLabelProp="label"
|
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
value={activeView}
|
value={activeView}
|
||||||
disabled={open || isAddingNewProfile} // Disable the Select box when the popover is open or adding a new profile
|
disabled={open || isAddingNewProfile} // Disable the Select box when the popover is open or adding a new profile
|
||||||
|
optionLabelProp="label"
|
||||||
>
|
>
|
||||||
{bodyshop?.production_config &&
|
{bodyshop?.production_config &&
|
||||||
bodyshop.production_config
|
bodyshop.production_config
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { HolderOutlined, SyncOutlined } from "@ant-design/icons";
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import ReactDragListView from "react-drag-listview";
|
import { closestCenter, DndContext, DragOverlay, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
|
||||||
|
import { arrayMove, horizontalListSortingStrategy, SortableContext, useSortable } from "@dnd-kit/sortable";
|
||||||
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
|
import { restrictToHorizontalAxis } from "@dnd-kit/modifiers";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
import { selectDarkMode } from "../../redux/application/application.selectors.js";
|
||||||
import Prompt from "../../utils/prompt.js";
|
import Prompt from "../../utils/prompt.js";
|
||||||
import AlertComponent from "../alert/alert.component.jsx";
|
import AlertComponent from "../alert/alert.component.jsx";
|
||||||
import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
|
import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
|
||||||
@@ -23,12 +27,81 @@ import { logImEXEvent } from "../../firebase/firebase.utils.js";
|
|||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
technician: selectTechnician,
|
technician: selectTechnician,
|
||||||
currentUser: selectCurrentUser
|
currentUser: selectCurrentUser,
|
||||||
|
isDarkMode: selectDarkMode
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) {
|
// Draggable header cell component - combines drag and resize
|
||||||
|
function DraggableHeaderCell(props) {
|
||||||
|
const { children, columnKey, onResize, width, ...restProps } = props;
|
||||||
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||||
|
id: columnKey,
|
||||||
|
disabled: !columnKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
...restProps.style,
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition,
|
||||||
|
opacity: isDragging ? 0.3 : 1,
|
||||||
|
userSelect: "none",
|
||||||
|
textAlign: "left"
|
||||||
|
};
|
||||||
|
|
||||||
|
// If no columnKey, render as regular header
|
||||||
|
if (!columnKey) {
|
||||||
|
return <ResizeableTitle {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only apply drag listeners to elements with data-drag-handle attribute
|
||||||
|
const filteredListeners = listeners
|
||||||
|
? {
|
||||||
|
onPointerDown: (e) => {
|
||||||
|
// Only trigger drag if clicking on the drag handle
|
||||||
|
if (e.target.closest('[data-drag-handle="true"]')) {
|
||||||
|
listeners.onPointerDown?.(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
// Combine drag functionality with resize
|
||||||
|
return (
|
||||||
|
<ResizeableTitle
|
||||||
|
{...restProps}
|
||||||
|
ref={setNodeRef}
|
||||||
|
style={style}
|
||||||
|
onResize={onResize}
|
||||||
|
width={width}
|
||||||
|
dragAttributes={attributes}
|
||||||
|
dragListeners={filteredListeners}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ResizeableTitle>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser, isDarkMode }) {
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||||
|
|
||||||
|
// NEW: smoother resize
|
||||||
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
|
const resizeRafRef = useRef(null);
|
||||||
|
const pendingResizeRef = useRef(null);
|
||||||
|
|
||||||
|
const [activeId, setActiveId] = useState(null);
|
||||||
|
|
||||||
|
const MIN_COL_WIDTH = 20;
|
||||||
|
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(PointerSensor, {
|
||||||
|
activationConstraint: {
|
||||||
|
distance: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
||||||
} = useTreatmentsWithConfig({
|
} = useTreatmentsWithConfig({
|
||||||
@@ -36,8 +109,10 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
names: ["Production_List_Status_Colors", "Enhanced_Payroll"],
|
names: ["Production_List_Status_Colors", "Enhanced_Payroll"],
|
||||||
splitKey: bodyshop.imexshopid
|
splitKey: bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
|
||||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||||
const defaultView = assoc?.default_prod_list_view;
|
const defaultView = assoc?.default_prod_list_view;
|
||||||
|
|
||||||
const initialStateRef = useRef(
|
const initialStateRef = useRef(
|
||||||
(bodyshop.production_config &&
|
(bodyshop.production_config &&
|
||||||
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
||||||
@@ -46,6 +121,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
filteredInfo: { text: "" }
|
filteredInfo: { text: "" }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialColumnsRef = useRef(
|
const initialColumnsRef = useRef(
|
||||||
(initialStateRef.current &&
|
(initialStateRef.current &&
|
||||||
bodyshop?.production_config
|
bodyshop?.production_config
|
||||||
@@ -66,14 +142,36 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
})) ||
|
})) ||
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [state, setState] = useState(initialStateRef.current);
|
const [state, setState] = useState(initialStateRef.current);
|
||||||
const [columns, setColumns] = useState(initialColumnsRef.current);
|
const [columns, setColumns] = useState(initialColumnsRef.current);
|
||||||
|
|
||||||
|
const scrollX = useMemo(() => {
|
||||||
|
// keep scroll width aligned with the actual column widths so AntD doesn't clamp at a fixed floor
|
||||||
|
const sum = columns.reduce((acc, c) => acc + (c.width ?? 100), 0);
|
||||||
|
return Math.max(sum, 1);
|
||||||
|
}, [columns]);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const matchingColumnConfig = useMemo(() => {
|
const matchingColumnConfig = useMemo(() => {
|
||||||
return bodyshop?.production_config?.find((p) => p.name === defaultView);
|
return bodyshop?.production_config?.find((p) => p.name === defaultView);
|
||||||
}, [bodyshop.production_config, defaultView]);
|
}, [bodyshop.production_config, defaultView]);
|
||||||
|
|
||||||
|
// NEW: cleanup RAF on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// NEW: while resizing, don’t regenerate columns
|
||||||
|
if (isResizing) return;
|
||||||
|
|
||||||
|
// NEW: bail early BEFORE expensive ProductionListColumns(...) call
|
||||||
|
if (!_.isEqual(initialColumnsRef.current, columns)) return;
|
||||||
|
|
||||||
const newColumns =
|
const newColumns =
|
||||||
matchingColumnConfig?.columns.columnKeys.map((k) => {
|
matchingColumnConfig?.columns.columnKeys.map((k) => {
|
||||||
return {
|
return {
|
||||||
@@ -89,10 +187,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
width: k.width ?? 100
|
width: k.width ?? 100
|
||||||
};
|
};
|
||||||
}) || [];
|
}) || [];
|
||||||
// Only update columns if they haven't been manually changed by the user
|
|
||||||
if (_.isEqual(initialColumnsRef.current, columns)) {
|
setColumns(newColumns);
|
||||||
setColumns(newColumns);
|
|
||||||
}
|
|
||||||
}, [
|
}, [
|
||||||
matchingColumnConfig,
|
matchingColumnConfig,
|
||||||
bodyshop,
|
bodyshop,
|
||||||
@@ -102,7 +198,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
Production_List_Status_Colors,
|
Production_List_Status_Colors,
|
||||||
refetch,
|
refetch,
|
||||||
state,
|
state,
|
||||||
columns
|
columns,
|
||||||
|
isResizing
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
@@ -118,17 +215,30 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
logImEXEvent("production_list_sort_filter", { pagination, filters, sorter });
|
logImEXEvent("production_list_sort_filter", { pagination, filters, sorter });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragEnd = (fromIndex, toIndex) => {
|
const onDragStart = ({ active }) => {
|
||||||
if (fromIndex === toIndex) return;
|
setActiveId(active.id);
|
||||||
const columnsCopy = [...columns];
|
};
|
||||||
const [movedItem] = columnsCopy.splice(fromIndex, 1);
|
|
||||||
columnsCopy.splice(toIndex, 0, movedItem);
|
const onDragEnd = ({ active, over }) => {
|
||||||
if (!_.isEqual(columnsCopy, columns)) {
|
setActiveId(null);
|
||||||
setColumns(columnsCopy);
|
if (!over || active.id === over.id) return;
|
||||||
setHasUnsavedChanges(true);
|
|
||||||
|
const oldIndex = columns.findIndex((col) => col.key === active.id);
|
||||||
|
const newIndex = columns.findIndex((col) => col.key === over.id);
|
||||||
|
|
||||||
|
if (oldIndex !== -1 && newIndex !== -1) {
|
||||||
|
const newColumns = arrayMove(columns, oldIndex, newIndex);
|
||||||
|
if (!_.isEqual(newColumns, columns)) {
|
||||||
|
setColumns(newColumns);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDragCancel = () => {
|
||||||
|
setActiveId(null);
|
||||||
|
};
|
||||||
|
|
||||||
const removeColumn = (e) => {
|
const removeColumn = (e) => {
|
||||||
const { key } = e;
|
const { key } = e;
|
||||||
const newColumns = columns.filter((i) => i.key !== key);
|
const newColumns = columns.filter((i) => i.key !== key);
|
||||||
@@ -139,19 +249,55 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
logImEXEvent("production_list_remove_column", { key });
|
logImEXEvent("production_list_remove_column", { key });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResize =
|
// NEW: commit widths via rAF (less jank)
|
||||||
(index) =>
|
const applyColumnWidth = useCallback((columnKey, width) => {
|
||||||
(e, { size }) => {
|
const nextWidth = Math.max(MIN_COL_WIDTH, Math.round(width));
|
||||||
const nextColumns = [...columns];
|
setColumns((prev) => {
|
||||||
nextColumns[index] = {
|
const idx = prev.findIndex((c) => c.key === columnKey);
|
||||||
...nextColumns[index],
|
if (idx === -1) return prev;
|
||||||
width: size.width
|
|
||||||
};
|
const currentWidth = prev[idx].width ?? 100;
|
||||||
if (!_.isEqual(nextColumns, columns)) {
|
if (currentWidth === nextWidth) return prev;
|
||||||
setColumns(nextColumns);
|
|
||||||
|
const next = prev.slice();
|
||||||
|
next[idx] = { ...next[idx], width: nextWidth };
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleResize = useCallback(
|
||||||
|
(columnKey) =>
|
||||||
|
(e, { size }) => {
|
||||||
|
pendingResizeRef.current = { columnKey, width: size.width };
|
||||||
|
|
||||||
|
if (resizeRafRef.current) return;
|
||||||
|
resizeRafRef.current = requestAnimationFrame(() => {
|
||||||
|
resizeRafRef.current = null;
|
||||||
|
const pending = pendingResizeRef.current;
|
||||||
|
if (!pending) return;
|
||||||
|
applyColumnWidth(pending.columnKey, pending.width);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[applyColumnWidth]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleResizeStart = useCallback(() => {
|
||||||
|
setIsResizing(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleResizeStop = useCallback(
|
||||||
|
(columnKey) =>
|
||||||
|
(e, { size }) => {
|
||||||
|
setIsResizing(false);
|
||||||
|
|
||||||
|
// Ensure final width is committed
|
||||||
|
applyColumnWidth(columnKey, size.width);
|
||||||
|
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
}
|
logImEXEvent("production_list_resize_column", { key: columnKey, width: size.width });
|
||||||
};
|
},
|
||||||
|
[applyColumnWidth]
|
||||||
|
);
|
||||||
|
|
||||||
const addColumn = (newColumn) => {
|
const addColumn = (newColumn) => {
|
||||||
const updatedColumns = [...columns, newColumn];
|
const updatedColumns = [...columns, newColumn];
|
||||||
@@ -163,19 +309,53 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
};
|
};
|
||||||
|
|
||||||
const headerItem = (col) => {
|
const headerItem = (col) => {
|
||||||
const menu = {
|
const menu = { onClick: removeColumn, items: [{ key: col.key, label: t("production.actions.removecolumn") }] };
|
||||||
onClick: removeColumn,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
key: col.key,
|
|
||||||
label: t("production.actions.removecolumn")
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<Dropdown className="prod-header-dropdown" menu={menu} trigger={["contextMenu"]}>
|
<div
|
||||||
<span>{col.title}</span>
|
style={{
|
||||||
</Dropdown>
|
display: "flex",
|
||||||
|
alignItems: "left",
|
||||||
|
width: "100%",
|
||||||
|
userSelect: "none",
|
||||||
|
minWidth: 0 // critical: allow the flex row to shrink
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="drag-handle-trigger"
|
||||||
|
data-drag-handle="true"
|
||||||
|
style={{
|
||||||
|
marginRight: 8,
|
||||||
|
color: "#999",
|
||||||
|
cursor: "grab",
|
||||||
|
padding: 4,
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "left",
|
||||||
|
userSelect: "none",
|
||||||
|
flex: "0 0 auto"
|
||||||
|
}}
|
||||||
|
title="Drag to reorder column"
|
||||||
|
>
|
||||||
|
<HolderOutlined />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Dropdown className="prod-header-dropdown" menu={menu} trigger={["contextMenu"]}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
flex: "1 1 auto",
|
||||||
|
minWidth: 0, // critical: allow text to shrink
|
||||||
|
overflow: "hidden", // clip
|
||||||
|
textOverflow: "ellipsis", // show …
|
||||||
|
whiteSpace: "nowrap", // keep single line
|
||||||
|
cursor: "default",
|
||||||
|
userSelect: "none",
|
||||||
|
display: "block"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{col.title}
|
||||||
|
</span>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -274,6 +454,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
onSave={() => {
|
onSave={() => {
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
initialStateRef.current = state;
|
initialStateRef.current = state;
|
||||||
|
|
||||||
|
// NEW: after saving, treat current columns as the baseline
|
||||||
|
initialColumnsRef.current = columns;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
@@ -286,60 +469,104 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ProductionListDetail jobs={dataSource} />
|
<ProductionListDetail jobs={dataSource} />
|
||||||
<ReactDragListView.DragColumn onDragEnd={onDragEnd} nodeSelector="th" handleSelector=".prod-header-dropdown">
|
<DndContext
|
||||||
<Table
|
sensors={sensors}
|
||||||
sticky
|
onDragStart={onDragStart}
|
||||||
pagination={false}
|
onDragEnd={onDragEnd}
|
||||||
size="small"
|
onDragCancel={onDragCancel}
|
||||||
{...(Production_List_Status_Colors.treatment === "on" && {
|
collisionDetection={closestCenter}
|
||||||
onRow: (record, index) => {
|
modifiers={[restrictToHorizontalAxis]}
|
||||||
if (!bodyshop.md_ro_statuses.production_colors) return null;
|
>
|
||||||
const color = bodyshop.md_ro_statuses.production_colors.find((x) => x.status === record.status);
|
<SortableContext items={columns.map((col) => col.key)} strategy={horizontalListSortingStrategy}>
|
||||||
if (!color) {
|
<Table
|
||||||
if (index % 2 === 0)
|
sticky
|
||||||
|
tableLayout="fixed"
|
||||||
|
className="prod-list-table"
|
||||||
|
pagination={false}
|
||||||
|
size="small"
|
||||||
|
{...(Production_List_Status_Colors.treatment === "on" &&
|
||||||
|
!isResizing && {
|
||||||
|
onRow: (record, index) => {
|
||||||
|
if (!bodyshop.md_ro_statuses.production_colors) return null;
|
||||||
|
const color = bodyshop.md_ro_statuses.production_colors.find((x) => x.status === record.status);
|
||||||
|
if (!color) {
|
||||||
|
if (index % 2 === 0)
|
||||||
|
return {
|
||||||
|
style: {
|
||||||
|
backgroundColor: "var(--table-row-even-bg)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
|
className: "rowWithColor",
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: "var(--table-row-even-bg)"
|
"--bgColor": color.color
|
||||||
|
? `rgba(${color.color.r},${color.color.g},${color.color.b},${color.color.a || 1})`
|
||||||
|
: "var(--status-row-bg-fallback)"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
className: "rowWithColor",
|
|
||||||
style: {
|
|
||||||
"--bgColor": color.color
|
|
||||||
? `rgba(${color.color.r},${color.color.g},${color.color.b},${color.color.a || 1})`
|
|
||||||
: "var(--status-row-bg-fallback)"
|
|
||||||
}
|
}
|
||||||
|
})}
|
||||||
|
components={{
|
||||||
|
header: {
|
||||||
|
cell: DraggableHeaderCell
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
columns={columns.map((c) => {
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
filteredValue: state.filteredInfo[c.key] || null,
|
||||||
|
sortOrder: state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
|
||||||
|
title: headerItem(c),
|
||||||
|
ellipsis: true,
|
||||||
|
width: c.width ?? 100,
|
||||||
|
onHeaderCell: (column) => ({
|
||||||
|
columnKey: column.key,
|
||||||
|
width: column.width,
|
||||||
|
onResize: handleResize(column.key),
|
||||||
|
onResizeStart: handleResizeStart,
|
||||||
|
onResizeStop: handleResizeStop(column.key)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
})}
|
||||||
})}
|
rowKey="id"
|
||||||
components={{
|
loading={loading}
|
||||||
header: {
|
dataSource={dataSource}
|
||||||
cell: ResizeableTitle
|
scroll={{ x: scrollX }}
|
||||||
}
|
onChange={handleTableChange}
|
||||||
}}
|
/>
|
||||||
columns={columns.map((c, index) => {
|
</SortableContext>
|
||||||
return {
|
|
||||||
...c,
|
<DragOverlay dropAnimation={null}>
|
||||||
filteredValue: state.filteredInfo[c.key] || null,
|
{activeId ? (
|
||||||
sortOrder: state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
|
<div
|
||||||
title: headerItem(c),
|
style={{
|
||||||
ellipsis: true,
|
backgroundColor: isDarkMode ? "#141414" : "white",
|
||||||
width: c.width ?? 100,
|
color: isDarkMode ? "white" : "#000",
|
||||||
onHeaderCell: (column) => ({
|
border: `2px solid ${isDarkMode ? "#177ddc" : "#1890ff"}`,
|
||||||
width: column.width,
|
borderRadius: "4px",
|
||||||
onResize: handleResize(index)
|
padding: "12px 16px",
|
||||||
})
|
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.25)",
|
||||||
};
|
cursor: "grabbing",
|
||||||
})}
|
display: "flex",
|
||||||
rowKey="id"
|
alignItems: "center",
|
||||||
loading={loading}
|
fontWeight: 500,
|
||||||
dataSource={dataSource}
|
minWidth: "120px"
|
||||||
scroll={{ x: 1000 }}
|
}}
|
||||||
onChange={handleTableChange}
|
>
|
||||||
/>
|
<HolderOutlined style={{ marginRight: "8px", color: isDarkMode ? "white" : "#000", fontSize: "16px" }} />
|
||||||
</ReactDragListView.DragColumn>
|
<span>
|
||||||
|
{(() => {
|
||||||
|
const col = columns.find((c) => c.key === activeId);
|
||||||
|
const title = typeof col?.title === "string" ? col.title : col?.dataIndex || col?.key || "Column";
|
||||||
|
return title;
|
||||||
|
})()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</DragOverlay>
|
||||||
|
</DndContext>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,37 @@
|
|||||||
|
import { forwardRef } from "react";
|
||||||
import { Resizable } from "react-resizable";
|
import { Resizable } from "react-resizable";
|
||||||
|
import "react-resizable/css/styles.css";
|
||||||
|
|
||||||
export default function ResizableComponent(props) {
|
const ResizableComponent = forwardRef((props, ref) => {
|
||||||
const { onResize, width, ...restProps } = props;
|
const { onResize, onResizeStart, onResizeStop, width, dragAttributes, dragListeners, ...restProps } = props;
|
||||||
|
|
||||||
if (!width) {
|
if (!width) {
|
||||||
return <th {...restProps} />;
|
return <th ref={ref} {...restProps} {...(dragAttributes || {})} {...(dragListeners || {})} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Resizable
|
<Resizable
|
||||||
width={width || 200}
|
width={width}
|
||||||
height={0}
|
height={0}
|
||||||
onResize={onResize}
|
onResize={onResize}
|
||||||
|
onResizeStart={onResizeStart}
|
||||||
|
onResizeStop={onResizeStop}
|
||||||
draggableOpts={{ enableUserSelectHack: false }}
|
draggableOpts={{ enableUserSelectHack: false }}
|
||||||
handle={
|
resizeHandles={["e"]}
|
||||||
|
axis="x"
|
||||||
|
handle={(axis, handleRef) => (
|
||||||
<span
|
<span
|
||||||
className="react-resizable-handle"
|
ref={handleRef}
|
||||||
onClick={(e) => {
|
className={`react-resizable-handle react-resizable-handle-${axis}`}
|
||||||
e.stopPropagation();
|
onClick={(e) => e.stopPropagation()}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
>
|
>
|
||||||
<th {...restProps} />
|
<th ref={ref} {...restProps} {...(dragAttributes || {})} {...(dragListeners || {})} />
|
||||||
</Resizable>
|
</Resizable>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
ResizableComponent.displayName = "ResizableComponent";
|
||||||
|
|
||||||
|
export default ResizableComponent;
|
||||||
|
|||||||
@@ -158,20 +158,28 @@ export function ScheduleJobModalComponent({
|
|||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
<Form.Item name="color" label={t("appointments.fields.color")}>
|
<Form.Item name="color" label={t("appointments.fields.color")}>
|
||||||
<Select allowClear>
|
<Select
|
||||||
{bodyshop.appt_colors &&
|
allowClear
|
||||||
bodyshop.appt_colors.map((color) => (
|
options={
|
||||||
<Select.Option style={{ color: color.color.hex }} key={color.color.hex} value={color.color.hex}>
|
bodyshop.appt_colors &&
|
||||||
{color.label}
|
bodyshop.appt_colors.map((color) => ({
|
||||||
</Select.Option>
|
value: color.color.hex,
|
||||||
))}
|
label: color.label
|
||||||
</Select>
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name={"alt_transport"} label={t("jobs.fields.alt_transport")}>
|
<Form.Item name={"alt_transport"} label={t("jobs.fields.alt_transport")}>
|
||||||
<Select allowClear>
|
<Select
|
||||||
{bodyshop.appt_alt_transport &&
|
allowClear
|
||||||
bodyshop.appt_alt_transport.map((alt) => <Select.Option key={alt}>{alt}</Select.Option>)}
|
options={
|
||||||
</Select>
|
bodyshop.appt_alt_transport &&
|
||||||
|
bodyshop.appt_alt_transport.map((alt) => ({
|
||||||
|
value: alt,
|
||||||
|
label: alt
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name={"note"} label={t("appointments.fields.note")}>
|
<Form.Item name={"note"} label={t("appointments.fields.note")}>
|
||||||
<Input />
|
<Input />
|
||||||
|
|||||||
@@ -120,13 +120,12 @@ export function ScheduleManualEvent({ bodyshop, event }) {
|
|||||||
<FormDateTimePickerComponent />
|
<FormDateTimePickerComponent />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("appointments.fields.color")} name="color">
|
<Form.Item label={t("appointments.fields.color")} name="color">
|
||||||
<Select>
|
<Select
|
||||||
{bodyshop.appt_colors.map((col, idx) => (
|
options={bodyshop.appt_colors.map((col) => ({
|
||||||
<Select.Option key={idx} value={col.color.hex}>
|
value: col.color.hex,
|
||||||
{col.label}
|
label: col.label
|
||||||
</Select.Option>
|
}))}
|
||||||
))}
|
/>
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|||||||
@@ -325,22 +325,20 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select
|
||||||
<Select.Option key={"shift"} value="timetickets.labels.shift">
|
options={[
|
||||||
{t("timetickets.labels.shift")}
|
{ value: "timetickets.labels.shift", label: t("timetickets.labels.shift") },
|
||||||
</Select.Option>
|
...(bodyshop.cdk_dealerid ||
|
||||||
|
bodyshop.pbs_serialnumber ||
|
||||||
{bodyshop.cdk_dealerid ||
|
bodyshop.rr_dealerid ||
|
||||||
bodyshop.pbs_serialnumber ||
|
Enhanced_Payroll.treatment === "on"
|
||||||
bodyshop.rr_dealerid ||
|
? CiecaSelect(false, true)
|
||||||
Enhanced_Payroll.treatment === "on"
|
: bodyshop.md_responsibility_centers.costs.map((c) => ({
|
||||||
? CiecaSelect(false, true)
|
value: c.name,
|
||||||
: bodyshop.md_responsibility_centers.costs.map((c) => (
|
label: c.name
|
||||||
<Select.Option key={c.name} value={c.name}>
|
})))
|
||||||
{c.name}
|
]}
|
||||||
</Select.Option>
|
/>
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("employees.fields.rate")}
|
label={t("employees.fields.rate")}
|
||||||
|
|||||||
@@ -1039,22 +1039,25 @@ export function ShopInfoGeneral({ form }) {
|
|||||||
key={`${index}mod_lbr_ty`}
|
key={`${index}mod_lbr_ty`}
|
||||||
name={[field.name, "mod_lbr_ty"]}
|
name={[field.name, "mod_lbr_ty"]}
|
||||||
>
|
>
|
||||||
<Select allowClear>
|
<Select
|
||||||
<Select.Option value="LAA">{t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
allowClear
|
||||||
<Select.Option value="LAB">{t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
options={[
|
||||||
<Select.Option value="LAD">{t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
{ value: "LAA", label: t("joblines.fields.lbr_types.LAA") },
|
||||||
<Select.Option value="LAE">{t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
{ value: "LAB", label: t("joblines.fields.lbr_types.LAB") },
|
||||||
<Select.Option value="LAF">{t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
{ value: "LAD", label: t("joblines.fields.lbr_types.LAD") },
|
||||||
<Select.Option value="LAG">{t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
{ value: "LAE", label: t("joblines.fields.lbr_types.LAE") },
|
||||||
<Select.Option value="LAM">{t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
{ value: "LAF", label: t("joblines.fields.lbr_types.LAF") },
|
||||||
<Select.Option value="LAR">{t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
{ value: "LAG", label: t("joblines.fields.lbr_types.LAG") },
|
||||||
<Select.Option value="LAS">{t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
{ value: "LAM", label: t("joblines.fields.lbr_types.LAM") },
|
||||||
<Select.Option value="LAU">{t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
{ value: "LAR", label: t("joblines.fields.lbr_types.LAR") },
|
||||||
<Select.Option value="LA1">{t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
{ value: "LAS", label: t("joblines.fields.lbr_types.LAS") },
|
||||||
<Select.Option value="LA2">{t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
{ value: "LAU", label: t("joblines.fields.lbr_types.LAU") },
|
||||||
<Select.Option value="LA3">{t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
{ value: "LA1", label: t("joblines.fields.lbr_types.LA1") },
|
||||||
<Select.Option value="LA4">{t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
{ value: "LA2", label: t("joblines.fields.lbr_types.LA2") },
|
||||||
</Select>
|
{ value: "LA3", label: t("joblines.fields.lbr_types.LA3") },
|
||||||
|
{ value: "LA4", label: t("joblines.fields.lbr_types.LA4") }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("joblines.fields.mod_lb_hrs")}
|
label={t("joblines.fields.mod_lb_hrs")}
|
||||||
@@ -1068,17 +1071,20 @@ export function ShopInfoGeneral({ form }) {
|
|||||||
key={`${index}part_type`}
|
key={`${index}part_type`}
|
||||||
name={[field.name, "part_type"]}
|
name={[field.name, "part_type"]}
|
||||||
>
|
>
|
||||||
<Select allowClear>
|
<Select
|
||||||
<Select.Option value="PAA">{t("joblines.fields.part_types.PAA")}</Select.Option>
|
allowClear
|
||||||
<Select.Option value="PAC">{t("joblines.fields.part_types.PAC")}</Select.Option>
|
options={[
|
||||||
<Select.Option value="PAE">{t("joblines.fields.part_types.PAE")}</Select.Option>
|
{ value: "PAA", label: t("joblines.fields.part_types.PAA") },
|
||||||
<Select.Option value="PAL">{t("joblines.fields.part_types.PAL")}</Select.Option>
|
{ value: "PAC", label: t("joblines.fields.part_types.PAC") },
|
||||||
<Select.Option value="PAM">{t("joblines.fields.part_types.PAM")}</Select.Option>
|
{ value: "PAE", label: t("joblines.fields.part_types.PAE") },
|
||||||
<Select.Option value="PAN">{t("joblines.fields.part_types.PAN")}</Select.Option>
|
{ value: "PAL", label: t("joblines.fields.part_types.PAL") },
|
||||||
<Select.Option value="PAO">{t("joblines.fields.part_types.PAO")}</Select.Option>
|
{ value: "PAM", label: t("joblines.fields.part_types.PAM") },
|
||||||
<Select.Option value="PAR">{t("joblines.fields.part_types.PAR")}</Select.Option>
|
{ value: "PAN", label: t("joblines.fields.part_types.PAN") },
|
||||||
<Select.Option value="PAS">{t("joblines.fields.part_types.PAS")}</Select.Option>
|
{ value: "PAO", label: t("joblines.fields.part_types.PAO") },
|
||||||
</Select>
|
{ value: "PAR", label: t("joblines.fields.part_types.PAR") },
|
||||||
|
{ value: "PAS", label: t("joblines.fields.part_types.PAS") }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("joblines.fields.oem_partno")}
|
label={t("joblines.fields.oem_partno")}
|
||||||
|
|||||||
@@ -51,13 +51,7 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={Object.keys(ConfigFormTypes).map((i) => ({ value: i, label: i }))} />
|
||||||
{Object.keys(ConfigFormTypes).map((i) => (
|
|
||||||
<Select.Option key={i} value={i}>
|
|
||||||
{i}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("jobs.fields.intake.label")}
|
label={t("jobs.fields.intake.label")}
|
||||||
@@ -156,13 +150,13 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="multiple">
|
<Select
|
||||||
{Object.keys(TemplateListGenerated).map((i) => (
|
mode="multiple"
|
||||||
<Select.Option key={TemplateListGenerated[i].key} value={TemplateListGenerated[i].key}>
|
options={Object.keys(TemplateListGenerated).map((i) => ({
|
||||||
{TemplateListGenerated[i].title}
|
value: TemplateListGenerated[i].key,
|
||||||
</Select.Option>
|
label: TemplateListGenerated[i].title
|
||||||
))}
|
}))}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["intakechecklist", "next_contact_hours"]}
|
name={["intakechecklist", "next_contact_hours"]}
|
||||||
@@ -205,13 +199,7 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={Object.keys(ConfigFormTypes).map((i) => ({ value: i, label: i }))} />
|
||||||
{Object.keys(ConfigFormTypes).map((i) => (
|
|
||||||
<Select.Option key={i} value={i}>
|
|
||||||
{i}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -310,13 +298,13 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="multiple">
|
<Select
|
||||||
{Object.keys(TemplateListGenerated).map((i) => (
|
mode="multiple"
|
||||||
<Select.Option key={TemplateListGenerated[i].key} value={TemplateListGenerated[i].key}>
|
options={Object.keys(TemplateListGenerated).map((i) => ({
|
||||||
{TemplateListGenerated[i].title}
|
value: TemplateListGenerated[i].key,
|
||||||
</Select.Option>
|
label: TemplateListGenerated[i].title
|
||||||
))}
|
}))}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["deliverchecklist", "actual_delivery"]}
|
name={["deliverchecklist", "actual_delivery"]}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -80,13 +80,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="multiple">
|
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "pre_production_statuses"]}
|
name={["md_ro_statuses", "pre_production_statuses"]}
|
||||||
@@ -99,13 +93,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="multiple">
|
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "production_statuses"]}
|
name={["md_ro_statuses", "production_statuses"]}
|
||||||
@@ -118,13 +106,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="multiple">
|
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "post_production_statuses"]}
|
name={["md_ro_statuses", "post_production_statuses"]}
|
||||||
@@ -137,13 +119,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="multiple">
|
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "ready_statuses"]}
|
name={["md_ro_statuses", "ready_statuses"]}
|
||||||
@@ -156,13 +132,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="multiple">
|
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "additional_board_statuses"]}
|
name={["md_ro_statuses", "additional_board_statuses"]}
|
||||||
@@ -175,13 +145,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="multiple">
|
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<LayoutFormRow noDivider>
|
<LayoutFormRow noDivider>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -194,13 +158,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_scheduled"]}
|
name={["md_ro_statuses", "default_scheduled"]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_arrived")}
|
label={t("bodyshop.fields.statuses.default_arrived")}
|
||||||
@@ -212,13 +170,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_arrived"]}
|
name={["md_ro_statuses", "default_arrived"]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_exported")}
|
label={t("bodyshop.fields.statuses.default_exported")}
|
||||||
@@ -230,13 +182,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_exported"]}
|
name={["md_ro_statuses", "default_exported"]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_imported")}
|
label={t("bodyshop.fields.statuses.default_imported")}
|
||||||
@@ -248,13 +194,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_imported"]}
|
name={["md_ro_statuses", "default_imported"]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_invoiced")}
|
label={t("bodyshop.fields.statuses.default_invoiced")}
|
||||||
@@ -266,13 +206,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_invoiced"]}
|
name={["md_ro_statuses", "default_invoiced"]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_completed")}
|
label={t("bodyshop.fields.statuses.default_completed")}
|
||||||
@@ -284,13 +218,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_completed"]}
|
name={["md_ro_statuses", "default_completed"]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_delivered")}
|
label={t("bodyshop.fields.statuses.default_delivered")}
|
||||||
@@ -302,13 +230,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_delivered"]}
|
name={["md_ro_statuses", "default_delivered"]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_void")}
|
label={t("bodyshop.fields.statuses.default_void")}
|
||||||
@@ -320,13 +242,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_void"]}
|
name={["md_ro_statuses", "default_void"]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={options.map((item) => ({ value: item, label: item }))} />
|
||||||
{options.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
{Production_List_Status_Colors.treatment === "on" && (
|
{Production_List_Status_Colors.treatment === "on" && (
|
||||||
@@ -352,13 +268,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select options={productionStatus.map((item) => ({ value: item, label: item }))} />
|
||||||
{productionStatus.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<DeleteFilled
|
<DeleteFilled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ export default function ShopInfoSpeedPrint() {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="multiple">
|
<Select
|
||||||
{Object.keys(TemplateListGenerated).map((key, idx) => (
|
mode="multiple"
|
||||||
<Select.Option key={idx} value={TemplateListGenerated[key].key}>
|
options={Object.keys(TemplateListGenerated).map((key) => ({
|
||||||
{TemplateListGenerated[key].title}
|
value: TemplateListGenerated[key].key,
|
||||||
</Select.Option>
|
label: TemplateListGenerated[key].title
|
||||||
))}
|
}))}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|||||||
@@ -43,85 +43,43 @@ export function ShopInfoIntellipay({ bodyshop, form }) {
|
|||||||
label={t("bodyshop.fields.intellipay_config.payment_map.visa")}
|
label={t("bodyshop.fields.intellipay_config.payment_map.visa")}
|
||||||
name={["intellipay_config", "payment_map", "visa"]}
|
name={["intellipay_config", "payment_map", "visa"]}
|
||||||
>
|
>
|
||||||
<Select showSearch>
|
<Select showSearch options={bodyshop.md_payment_types.map((item) => ({ value: item, label: item }))} />
|
||||||
{bodyshop.md_payment_types.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.intellipay_config.payment_map.mast")}
|
label={t("bodyshop.fields.intellipay_config.payment_map.mast")}
|
||||||
name={["intellipay_config", "payment_map", "mast"]}
|
name={["intellipay_config", "payment_map", "mast"]}
|
||||||
>
|
>
|
||||||
<Select showSearch>
|
<Select showSearch options={bodyshop.md_payment_types.map((item) => ({ value: item, label: item }))} />
|
||||||
{bodyshop.md_payment_types.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.intellipay_config.payment_map.amex")}
|
label={t("bodyshop.fields.intellipay_config.payment_map.amex")}
|
||||||
name={["intellipay_config", "payment_map", "amex"]}
|
name={["intellipay_config", "payment_map", "amex"]}
|
||||||
>
|
>
|
||||||
<Select showSearch>
|
<Select showSearch options={bodyshop.md_payment_types.map((item) => ({ value: item, label: item }))} />
|
||||||
{bodyshop.md_payment_types.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.intellipay_config.payment_map.disc")}
|
label={t("bodyshop.fields.intellipay_config.payment_map.disc")}
|
||||||
name={["intellipay_config", "payment_map", "disc"]}
|
name={["intellipay_config", "payment_map", "disc"]}
|
||||||
>
|
>
|
||||||
<Select showSearch>
|
<Select showSearch options={bodyshop.md_payment_types.map((item) => ({ value: item, label: item }))} />
|
||||||
{bodyshop.md_payment_types.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.intellipay_config.payment_map.dnrs")}
|
label={t("bodyshop.fields.intellipay_config.payment_map.dnrs")}
|
||||||
name={["intellipay_config", "payment_map", "dnrs"]}
|
name={["intellipay_config", "payment_map", "dnrs"]}
|
||||||
>
|
>
|
||||||
<Select showSearch>
|
<Select showSearch options={bodyshop.md_payment_types.map((item) => ({ value: item, label: item }))} />
|
||||||
{bodyshop.md_payment_types.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.intellipay_config.payment_map.jcb")}
|
label={t("bodyshop.fields.intellipay_config.payment_map.jcb")}
|
||||||
name={["intellipay_config", "payment_map", "jcb"]}
|
name={["intellipay_config", "payment_map", "jcb"]}
|
||||||
>
|
>
|
||||||
<Select showSearch>
|
<Select showSearch options={bodyshop.md_payment_types.map((item) => ({ value: item, label: item }))} />
|
||||||
{bodyshop.md_payment_types.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.intellipay_config.payment_map.intr")}
|
label={t("bodyshop.fields.intellipay_config.payment_map.intr")}
|
||||||
name={["intellipay_config", "payment_map", "intr"]}
|
name={["intellipay_config", "payment_map", "intr"]}
|
||||||
>
|
>
|
||||||
<Select showSearch>
|
<Select showSearch options={bodyshop.md_payment_types.map((item) => ({ value: item, label: item }))} />
|
||||||
{bodyshop.md_payment_types.map((item, idx) => (
|
|
||||||
<Select.Option key={idx} value={item}>
|
|
||||||
{item}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -57,21 +57,23 @@ export function TechClockInComponent({ form, bodyshop, technician }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select
|
||||||
{emps &&
|
options={
|
||||||
emps.rates.map((item) => (
|
emps &&
|
||||||
<Select.Option key={item.cost_center} value={item.cost_center}>
|
emps.rates.map((item) => ({
|
||||||
{item.cost_center === "timetickets.labels.shift"
|
value: item.cost_center,
|
||||||
|
label:
|
||||||
|
item.cost_center === "timetickets.labels.shift"
|
||||||
? t(item.cost_center)
|
? t(item.cost_center)
|
||||||
: bodyshop.cdk_dealerid ||
|
: bodyshop.cdk_dealerid ||
|
||||||
bodyshop.pbs_serialnumber ||
|
bodyshop.pbs_serialnumber ||
|
||||||
bodyshop.rr_dealerid ||
|
bodyshop.rr_dealerid ||
|
||||||
Enhanced_Payroll.treatment === "on"
|
Enhanced_Payroll.treatment === "on"
|
||||||
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
||||||
: item.cost_center}
|
: item.cost_center
|
||||||
</Select.Option>
|
}))
|
||||||
))}
|
}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|||||||
@@ -201,22 +201,22 @@ export function TechClockOffButton({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select disabled={isShiftTicket}>
|
<Select disabled={isShiftTicket}
|
||||||
{isShiftTicket ? (
|
options={
|
||||||
<Select.Option value="timetickets.labels.shift">{t("timetickets.labels.shift")}</Select.Option>
|
isShiftTicket
|
||||||
) : (
|
? [{ value: "timetickets.labels.shift", label: t("timetickets.labels.shift") }]
|
||||||
emps &&
|
: emps &&
|
||||||
emps.rates.map((item) => (
|
emps.rates.map((item) => ({
|
||||||
<Select.Option key={item.cost_center}>
|
value: item.cost_center,
|
||||||
{item.cost_center === "timetickets.labels.shift"
|
label:
|
||||||
? t(item.cost_center)
|
item.cost_center === "timetickets.labels.shift"
|
||||||
: hasDmsKey
|
? t(item.cost_center)
|
||||||
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
: hasDmsKey
|
||||||
: item.cost_center}
|
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
||||||
</Select.Option>
|
: item.cost_center
|
||||||
))
|
}))
|
||||||
)}
|
}
|
||||||
</Select>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{isShiftTicket ? (
|
{isShiftTicket ? (
|
||||||
@@ -232,11 +232,12 @@ export function TechClockOffButton({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select
|
||||||
{bodyshop.md_ro_statuses.production_statuses.map((item) => (
|
options={bodyshop.md_ro_statuses.production_statuses.map((item) => ({
|
||||||
<Select.Option key={item}></Select.Option>
|
value: item,
|
||||||
))}
|
label: item
|
||||||
</Select>
|
}))}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -91,21 +91,22 @@ export function TimeTicketModalComponent({
|
|||||||
value={value === "timetickets.labels.shift" ? t(value) : value}
|
value={value === "timetickets.labels.shift" ? t(value) : value}
|
||||||
{...props}
|
{...props}
|
||||||
disabled={value === "timetickets.labels.shift" || disabled}
|
disabled={value === "timetickets.labels.shift" || disabled}
|
||||||
>
|
options={
|
||||||
{emps &&
|
emps &&
|
||||||
emps.rates.map((item) => (
|
emps.rates.map((item) => ({
|
||||||
<Select.Option key={item.cost_center} value={item.cost_center}>
|
value: item.cost_center,
|
||||||
{item.cost_center === "timetickets.labels.shift"
|
label:
|
||||||
|
item.cost_center === "timetickets.labels.shift"
|
||||||
? t(item.cost_center)
|
? t(item.cost_center)
|
||||||
: bodyshop.cdk_dealerid ||
|
: bodyshop.cdk_dealerid ||
|
||||||
bodyshop.pbs_serialnumber ||
|
bodyshop.pbs_serialnumber ||
|
||||||
bodyshop.rr_dealerid ||
|
bodyshop.rr_dealerid ||
|
||||||
Enhanced_Payroll.treatment === "on"
|
Enhanced_Payroll.treatment === "on"
|
||||||
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
||||||
: item.cost_center}
|
: item.cost_center
|
||||||
</Select.Option>
|
}))
|
||||||
))}
|
}
|
||||||
</Select>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const MemoInput = ({ value, ...props }) => (
|
const MemoInput = ({ value, ...props }) => (
|
||||||
|
|||||||
@@ -20,13 +20,15 @@ export function TimeTicketShiftFormComponent() {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select
|
||||||
<Select.Option value="timetickets.labels.amshift">{t("timetickets.labels.amshift")}</Select.Option>
|
options={[
|
||||||
<Select.Option value="timetickets.labels.ambreak">{t("timetickets.labels.ambreak")}</Select.Option>
|
{ value: "timetickets.labels.amshift", label: t("timetickets.labels.amshift") },
|
||||||
<Select.Option value="timetickets.labels.lunch">{t("timetickets.labels.lunch")}</Select.Option>
|
{ value: "timetickets.labels.ambreak", label: t("timetickets.labels.ambreak") },
|
||||||
<Select.Option value="timetickets.labels.pmshift">{t("timetickets.labels.pmshift")}</Select.Option>
|
{ value: "timetickets.labels.lunch", label: t("timetickets.labels.lunch") },
|
||||||
<Select.Option value="timetickets.labels.pmbreak">{t("timetickets.labels.pmbreak")}</Select.Option>
|
{ value: "timetickets.labels.pmshift", label: t("timetickets.labels.pmshift") },
|
||||||
</Select>
|
{ value: "timetickets.labels.pmbreak", label: t("timetickets.labels.pmbreak") }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import {
|
|||||||
} from "../../graphql/vehicles.queries";
|
} from "../../graphql/vehicles.queries";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const VehicleSearchSelect = ({ value, onChange, onBlur, disabled, ref }) => {
|
const VehicleSearchSelect = ({ value, onChange, onBlur, disabled, ref }) => {
|
||||||
const [callSearch, { loading, error, data }] = useLazyQuery(SEARCH_VEHICLES_FOR_AUTOCOMPLETE);
|
const [callSearch, { loading, error, data }] = useLazyQuery(SEARCH_VEHICLES_FOR_AUTOCOMPLETE);
|
||||||
|
|
||||||
@@ -18,9 +16,10 @@ const VehicleSearchSelect = ({ value, onChange, onBlur, disabled, ref }) => {
|
|||||||
SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE
|
SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE
|
||||||
);
|
);
|
||||||
|
|
||||||
const executeSearch = (v) => {
|
const executeSearch = (variables) => {
|
||||||
if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch({ variables: v.variables });
|
if (variables?.search !== "" && variables?.search?.length >= 2) callSearch({ variables });
|
||||||
};
|
};
|
||||||
|
|
||||||
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
||||||
|
|
||||||
const handleSearch = (value) => {
|
const handleSearch = (value) => {
|
||||||
@@ -72,15 +71,12 @@ const VehicleSearchSelect = ({ value, onChange, onBlur, disabled, ref }) => {
|
|||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
>
|
options={theOptions?.map((o) => ({
|
||||||
{theOptions
|
key: o.id,
|
||||||
? theOptions.map((o) => (
|
value: o.id,
|
||||||
<Option key={o.id} value={o.id}>
|
label: `${o.v_vin || ""} ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${o.v_model_desc || ""} `
|
||||||
{`${o.v_vin || ""} ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${o.v_model_desc || ""} `}
|
}))}
|
||||||
</Option>
|
/>
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</Select>
|
|
||||||
{idLoading || loading ? <LoadingOutlined /> : null}
|
{idLoading || loading ? <LoadingOutlined /> : null}
|
||||||
{error ? <AlertComponent title={error.message} type="error" /> : null}
|
{error ? <AlertComponent title={error.message} type="error" /> : null}
|
||||||
{idError ? <AlertComponent title={idError.message} type="error" /> : null}
|
{idError ? <AlertComponent title={idError.message} type="error" /> : null}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { Select, Space, Tag } from "antd";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
// To be used as a form element only.
|
// To be used as a form element only.
|
||||||
|
|
||||||
const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, preferredMake, showPhone, ref }) => {
|
const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, preferredMake, showPhone, ref }) => {
|
||||||
@@ -21,10 +19,57 @@ const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, pref
|
|||||||
? options.filter((o) => o.favorite.filter((f) => f.toLowerCase() === preferredMake.toLowerCase()).length > 0)
|
? options.filter((o) => o.favorite.filter((f) => f.toLowerCase() === preferredMake.toLowerCase()).length > 0)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const formatOption = (o, isFavorite = false) => ({
|
||||||
|
key: isFavorite ? `favorite-${o.id}` : o.id,
|
||||||
|
value: o.id,
|
||||||
|
name: o.name,
|
||||||
|
discount: o.discount,
|
||||||
|
label: (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
width: "100%"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{o.name}
|
||||||
|
</div>
|
||||||
|
<Space style={{ marginLeft: "1rem" }}>
|
||||||
|
{isFavorite && <HeartOutlined style={{ color: "red" }} />}
|
||||||
|
{!isFavorite &&
|
||||||
|
o.tags?.map((tag, idx) => (
|
||||||
|
<Tag key={idx} style={{ marginLeft: "0.5rem" }}>
|
||||||
|
{tag}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
||||||
|
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
const allOptions = [
|
||||||
|
...(favorites?.map((o) => formatOption(o, true)) || []),
|
||||||
|
...(options?.map((o) => formatOption(o, false)) || [])
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
ref={ref}
|
ref={ref}
|
||||||
showSearch
|
showSearch={{
|
||||||
|
optionFilterProp: "name"
|
||||||
|
}}
|
||||||
value={option}
|
value={option}
|
||||||
style={{
|
style={{
|
||||||
width: "100%"
|
width: "100%"
|
||||||
@@ -59,76 +104,11 @@ const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, pref
|
|||||||
}}
|
}}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
onChange={setOption}
|
onChange={setOption}
|
||||||
optionFilterProp="name"
|
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
disabled={disabled || false}
|
disabled={disabled || false}
|
||||||
optionLabelProp="name"
|
optionLabelProp="name"
|
||||||
>
|
options={allOptions}
|
||||||
{favorites &&
|
/>
|
||||||
favorites.map((o) => (
|
|
||||||
<Option key={`favorite-${o.id}`} value={o.id} name={o.name} discount={o.discount}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
flexWrap: "nowrap",
|
|
||||||
width: "100%"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
minWidth: 0,
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
whiteSpace: "nowrap"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{o.name}
|
|
||||||
</div>
|
|
||||||
<Space style={{ marginLeft: "1rem" }}>
|
|
||||||
<HeartOutlined style={{ color: "red" }} />
|
|
||||||
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
|
||||||
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
{options &&
|
|
||||||
options.map((o) => (
|
|
||||||
<Option key={o.id} value={o.id} name={o.name} discount={o.discount}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
flexWrap: "nowrap",
|
|
||||||
width: "100%"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
minWidth: 0,
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
whiteSpace: "nowrap"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{o.name}
|
|
||||||
</div>
|
|
||||||
<Space style={{ marginLeft: "1rem" }}>
|
|
||||||
{o.tags?.map((tag, idx) => (
|
|
||||||
<Tag key={idx} style={{ marginLeft: "0.5rem" }}>
|
|
||||||
{tag}
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
|
||||||
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default VendorSearchSelect;
|
export default VendorSearchSelect;
|
||||||
|
|||||||
@@ -470,6 +470,9 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
clm_total
|
clm_total
|
||||||
comment
|
comment
|
||||||
converted
|
converted
|
||||||
|
dms_id
|
||||||
|
dms_customer_id
|
||||||
|
dms_advisor_id
|
||||||
csiinvites {
|
csiinvites {
|
||||||
completedon
|
completedon
|
||||||
id
|
id
|
||||||
@@ -491,6 +494,9 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
ded_status
|
ded_status
|
||||||
deliverchecklist
|
deliverchecklist
|
||||||
depreciation_taxes
|
depreciation_taxes
|
||||||
|
dms_id
|
||||||
|
dms_advisor_id
|
||||||
|
dms_customer_id
|
||||||
driveable
|
driveable
|
||||||
employee_body
|
employee_body
|
||||||
employee_body_rel {
|
employee_body_rel {
|
||||||
@@ -1995,6 +2001,9 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
|||||||
qb_multiple_payers
|
qb_multiple_payers
|
||||||
lbr_adjustments
|
lbr_adjustments
|
||||||
ownr_ea
|
ownr_ea
|
||||||
|
dms_id
|
||||||
|
dms_customer_id
|
||||||
|
dms_advisor_id
|
||||||
payments {
|
payments {
|
||||||
amount
|
amount
|
||||||
created_at
|
created_at
|
||||||
@@ -2216,6 +2225,9 @@ export const QUERY_JOB_EXPORT_DMS = gql`
|
|||||||
plate_no
|
plate_no
|
||||||
plate_st
|
plate_st
|
||||||
ownr_co_nm
|
ownr_co_nm
|
||||||
|
dms_id
|
||||||
|
dms_customer_id
|
||||||
|
dms_advisor_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -119,12 +119,13 @@ export function DmsContainer({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
setLogLevel(value);
|
setLogLevel(value);
|
||||||
socket.emit("set-log-level", value);
|
socket.emit("set-log-level", value);
|
||||||
}}
|
}}
|
||||||
>
|
options={[
|
||||||
<Select.Option key="DEBUG">DEBUG</Select.Option>
|
{ key: "DEBUG", value: "DEBUG", label: "DEBUG" },
|
||||||
<Select.Option key="INFO">INFO</Select.Option>
|
{ key: "INFO", value: "INFO", label: "INFO" },
|
||||||
<Select.Option key="WARN">WARN</Select.Option>
|
{ key: "WARN", value: "WARN", label: "WARN" },
|
||||||
<Select.Option key="ERROR">ERROR</Select.Option>
|
{ key: "ERROR", value: "ERROR", label: "ERROR" }
|
||||||
</Select>
|
]}
|
||||||
|
/>
|
||||||
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -426,6 +426,24 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
|||||||
|
|
||||||
if (data.jobs_by_pk?.date_exported) return <Result status="warning" title={t("dms.errors.alreadyexported")} />;
|
if (data.jobs_by_pk?.date_exported) return <Result status="warning" title={t("dms.errors.alreadyexported")} />;
|
||||||
|
|
||||||
|
// Check if Reynolds mode requires early RO
|
||||||
|
const hasEarlyRO = !!(data.jobs_by_pk?.dms_id && data.jobs_by_pk?.dms_customer_id && data.jobs_by_pk?.dms_advisor_id);
|
||||||
|
|
||||||
|
if (isRrMode && !hasEarlyRO) {
|
||||||
|
return (
|
||||||
|
<Result
|
||||||
|
status="warning"
|
||||||
|
title={t("dms.errors.earlyrorequired")}
|
||||||
|
subTitle={t("dms.errors.earlyrorequired.message")}
|
||||||
|
extra={
|
||||||
|
<Link to={`/manage/jobs/${jobId}/admin`}>
|
||||||
|
<Button type="primary">{t("general.actions.gotoadmin")}</Button>
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<AlertComponent style={{ marginBottom: 10 }} title={bannerMessage} type="warning" showIcon closable />
|
<AlertComponent style={{ marginBottom: 10 }} title={bannerMessage} type="warning" showIcon closable />
|
||||||
@@ -486,6 +504,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
|||||||
|
|
||||||
<DmsCustomerSelector
|
<DmsCustomerSelector
|
||||||
jobid={jobId}
|
jobid={jobId}
|
||||||
|
job={data?.jobs_by_pk}
|
||||||
bodyshop={bodyshop}
|
bodyshop={bodyshop}
|
||||||
socket={activeSocket}
|
socket={activeSocket}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
@@ -522,13 +541,14 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
|||||||
setLogLevel(value);
|
setLogLevel(value);
|
||||||
setActiveLogLevel(value);
|
setActiveLogLevel(value);
|
||||||
}}
|
}}
|
||||||
>
|
options={[
|
||||||
<Select.Option key="SILLY">SILLY</Select.Option>
|
{ key: "SILLY", value: "SILLY", label: "SILLY" },
|
||||||
<Select.Option key="DEBUG">DEBUG</Select.Option>
|
{ key: "DEBUG", value: "DEBUG", label: "DEBUG" },
|
||||||
<Select.Option key="INFO">INFO</Select.Option>
|
{ key: "INFO", value: "INFO", label: "INFO" },
|
||||||
<Select.Option key="WARN">WARN</Select.Option>
|
{ key: "WARN", value: "WARN", label: "WARN" },
|
||||||
<Select.Option key="ERROR">ERROR</Select.Option>
|
{ key: "ERROR", value: "ERROR", label: "ERROR" }
|
||||||
</Select>
|
]}
|
||||||
|
/>
|
||||||
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { useQuery } from "@apollo/client/react";
|
import { useMutation, useQuery } from "@apollo/client/react";
|
||||||
import { Card, Col, Result, Row, Space, Typography } from "antd";
|
import { Button, Card, Col, Form, Input, Modal, Result, Row, Select, Space, Switch, Typography } from "antd";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
||||||
|
import { some } from "lodash";
|
||||||
|
import axios from "axios";
|
||||||
import AlertComponent from "../../components/alert/alert.component";
|
import AlertComponent from "../../components/alert/alert.component";
|
||||||
import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component";
|
import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component";
|
||||||
import ScoreboardAddButton from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
|
import ScoreboardAddButton from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
|
||||||
@@ -19,13 +22,26 @@ import JobsAdminRemoveAR from "../../components/jobs-admin-remove-ar/jobs-admin-
|
|||||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||||
import NotFound from "../../components/not-found/not-found.component";
|
import NotFound from "../../components/not-found/not-found.component";
|
||||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||||
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
|
import RREarlyROModal from "../../components/dms-post-form/rr-early-ro-modal";
|
||||||
|
import { GET_JOB_BY_PK, CONVERT_JOB_TO_RO } from "../../graphql/jobs.queries";
|
||||||
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket";
|
||||||
|
import { useNotification } from "../../contexts/Notifications/notificationContext";
|
||||||
|
import { DMS_MAP, getDmsMode } from "../../utils/dmsUtils";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
|
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||||
|
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||||
});
|
});
|
||||||
|
|
||||||
const colSpan = {
|
const colSpan = {
|
||||||
@@ -39,14 +55,36 @@ const cardStyle = {
|
|||||||
height: "100%"
|
height: "100%"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
|
export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader, bodyshop, insertAuditTrail }) {
|
||||||
const { jobId } = useParams();
|
const { jobId } = useParams();
|
||||||
const { loading, error, data } = useQuery(GET_JOB_BY_PK, {
|
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
|
||||||
variables: { id: jobId },
|
variables: { id: jobId },
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only"
|
nextFetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { socket } = useSocket(); // Extract socket from context
|
||||||
|
const notification = useNotification();
|
||||||
|
const [showEarlyROModal, setShowEarlyROModal] = useState(false);
|
||||||
|
const [showConvertModal, setShowConvertModal] = useState(false);
|
||||||
|
const [convertLoading, setConvertLoading] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
|
||||||
|
const allFormValues = Form.useWatch([], form);
|
||||||
|
|
||||||
|
// Get Fortellis treatment for proper DMS mode detection
|
||||||
|
const {
|
||||||
|
treatments: { Fortellis }
|
||||||
|
} = useTreatmentsWithConfig({
|
||||||
|
attributes: {},
|
||||||
|
names: ["Fortellis"],
|
||||||
|
splitKey: bodyshop?.imexshopid
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if bodyshop has Reynolds integration using the proper getDmsMode function
|
||||||
|
const dmsMode = getDmsMode(bodyshop, Fortellis.treatment);
|
||||||
|
const isReynoldsMode = dmsMode === DMS_MAP.reynolds;
|
||||||
|
const job = data?.jobs_by_pk;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedHeader("activejobs");
|
setSelectedHeader("activejobs");
|
||||||
document.title = t("titles.jobs-admin", {
|
document.title = t("titles.jobs-admin", {
|
||||||
@@ -75,6 +113,55 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
]);
|
]);
|
||||||
}, [setBreadcrumbs, t, jobId, data, setSelectedHeader]);
|
}, [setBreadcrumbs, t, jobId, data, setSelectedHeader]);
|
||||||
|
|
||||||
|
const handleEarlyROSuccess = (result) => {
|
||||||
|
notification.success({
|
||||||
|
title: t("jobs.successes.early_ro_created"),
|
||||||
|
description: `RO Number: ${result.roNumber || "N/A"}`
|
||||||
|
});
|
||||||
|
setShowEarlyROModal(false);
|
||||||
|
refetch?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConvert = async ({ employee_csr, category, ...values }) => {
|
||||||
|
if (!job?.id) return;
|
||||||
|
setConvertLoading(true);
|
||||||
|
const res = await mutationConvertJob({
|
||||||
|
variables: {
|
||||||
|
jobId: job.id,
|
||||||
|
job: {
|
||||||
|
converted: true,
|
||||||
|
...(bodyshop?.enforce_conversion_csr ? { employee_csr } : {}),
|
||||||
|
...(bodyshop?.enforce_conversion_category ? { category } : {}),
|
||||||
|
...values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (values.ca_gst_registrant) {
|
||||||
|
await axios.post("/job/totalsssu", {
|
||||||
|
id: job.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.errors) {
|
||||||
|
refetch();
|
||||||
|
notification.success({
|
||||||
|
title: t("jobs.successes.converted")
|
||||||
|
});
|
||||||
|
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.jobconverted(res.data.update_jobs.returning[0].ro_number),
|
||||||
|
type: "jobconverted"
|
||||||
|
});
|
||||||
|
|
||||||
|
setShowConvertModal(false);
|
||||||
|
}
|
||||||
|
setConvertLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitDisabled = useCallback(() => some(allFormValues, (v) => v === undefined), [allFormValues]);
|
||||||
|
|
||||||
if (loading) return <LoadingSpinner />;
|
if (loading) return <LoadingSpinner />;
|
||||||
if (error) return <AlertComponent title={error.message} type="error" />;
|
if (error) return <AlertComponent title={error.message} type="error" />;
|
||||||
if (!data.jobs_by_pk) return <NotFound />;
|
if (!data.jobs_by_pk) return <NotFound />;
|
||||||
@@ -99,6 +186,16 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
<JobsAdminUnvoid job={data ? data.jobs_by_pk : {}} />
|
<JobsAdminUnvoid job={data ? data.jobs_by_pk : {}} />
|
||||||
<JobsAdminStatus job={data ? data.jobs_by_pk : {}} />
|
<JobsAdminStatus job={data ? data.jobs_by_pk : {}} />
|
||||||
<JobsAdminRemoveAR job={data ? data.jobs_by_pk : {}} />
|
<JobsAdminRemoveAR job={data ? data.jobs_by_pk : {}} />
|
||||||
|
{isReynoldsMode && job?.converted && !job?.dms_id && !job?.dms_customer_id && !job?.dms_advisor_id && (
|
||||||
|
<Button className="ant-btn ant-btn-default" onClick={() => setShowEarlyROModal(true)}>
|
||||||
|
{t("jobs.actions.dms.createearlyro", "Create RR RO")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{isReynoldsMode && !job?.converted && !job?.dms_id && (
|
||||||
|
<Button type="primary" danger onClick={() => setShowConvertModal(true)}>
|
||||||
|
{t("jobs.actions.convertwithoutearlyro", "Convert without Early RO")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -124,8 +221,173 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
{/* Early RO Modal */}
|
||||||
|
<RREarlyROModal
|
||||||
|
open={showEarlyROModal}
|
||||||
|
onClose={() => setShowEarlyROModal(false)}
|
||||||
|
onSuccess={handleEarlyROSuccess}
|
||||||
|
bodyshop={bodyshop}
|
||||||
|
socket={socket}
|
||||||
|
job={job}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Convert without Early RO Modal */}
|
||||||
|
<Modal
|
||||||
|
open={showConvertModal}
|
||||||
|
onCancel={() => setShowConvertModal(false)}
|
||||||
|
title={t("jobs.actions.convertwithoutearlyro", "Convert without Early RO")}
|
||||||
|
footer={null}
|
||||||
|
width={700}
|
||||||
|
destroyOnHidden
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
form={form}
|
||||||
|
onFinish={handleConvert}
|
||||||
|
initialValues={{
|
||||||
|
driveable: true,
|
||||||
|
towin: job?.towin,
|
||||||
|
ca_gst_registrant: job?.ca_gst_registrant,
|
||||||
|
employee_csr: job?.employee_csr,
|
||||||
|
category: job?.category,
|
||||||
|
referral_source: job?.referral_source,
|
||||||
|
referral_source_extra: job?.referral_source_extra ?? ""
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name={["ins_co_nm"]}
|
||||||
|
label={t("jobs.fields.ins_co_nm")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select showSearch>
|
||||||
|
{bodyshop?.md_ins_cos?.map((s, i) => (
|
||||||
|
<Select.Option key={i} value={s.name}>
|
||||||
|
{s.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
{bodyshop?.enforce_class && (
|
||||||
|
<Form.Item
|
||||||
|
name={"class"}
|
||||||
|
label={t("jobs.fields.class")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: bodyshop.enforce_class
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
{bodyshop?.md_classes?.map((s) => (
|
||||||
|
<Select.Option key={s} value={s}>
|
||||||
|
{s}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{bodyshop?.enforce_referral && (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name={"referral_source"}
|
||||||
|
label={t("jobs.fields.referralsource")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: bodyshop.enforce_referral
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
{bodyshop?.md_referral_sources?.map((s) => (
|
||||||
|
<Select.Option key={s} value={s}>
|
||||||
|
{s}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.referral_source_extra")} name="referral_source_extra">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{bodyshop?.enforce_conversion_csr && (
|
||||||
|
<Form.Item
|
||||||
|
name={"employee_csr"}
|
||||||
|
label={t(
|
||||||
|
InstanceRenderManager({
|
||||||
|
imex: "jobs.fields.employee_csr",
|
||||||
|
rome: "jobs.fields.employee_csr_writer"
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: bodyshop.enforce_conversion_csr
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
showSearch={{
|
||||||
|
optionFilterProp: "children",
|
||||||
|
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}}
|
||||||
|
style={{ width: 200 }}
|
||||||
|
>
|
||||||
|
{bodyshop?.employees
|
||||||
|
?.filter((emp) => emp.active)
|
||||||
|
?.map((emp) => (
|
||||||
|
<Select.Option value={emp.id} key={emp.id} name={`${emp.first_name} ${emp.last_name}`}>
|
||||||
|
{`${emp.first_name} ${emp.last_name}`}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{bodyshop?.enforce_conversion_category && (
|
||||||
|
<Form.Item
|
||||||
|
name={"category"}
|
||||||
|
label={t("jobs.fields.category")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: bodyshop.enforce_conversion_category
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select allowClear>
|
||||||
|
{bodyshop?.md_categories?.map((s) => (
|
||||||
|
<Select.Option key={s} value={s}>
|
||||||
|
{s}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{bodyshop?.region_config?.toLowerCase().startsWith("ca") && (
|
||||||
|
<Form.Item label={t("jobs.fields.ca_gst_registrant")} name="ca_gst_registrant" valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
<Form.Item label={t("jobs.fields.driveable")} name="driveable" valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.towin")} name="towin" valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Space wrap style={{ marginTop: 16 }}>
|
||||||
|
<Button disabled={submitDisabled()} type="primary" danger onClick={() => form.submit()} loading={convertLoading}>
|
||||||
|
{t("jobs.actions.convert")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setShowConvertModal(false)}>{t("general.actions.close")}</Button>
|
||||||
|
</Space>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
</RbacWrapper>
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(JobsCloseContainer);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseContainer);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
|
Modal,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
Row,
|
Row,
|
||||||
Select,
|
Select,
|
||||||
@@ -42,7 +43,7 @@ import { setModalContext } from "../../redux/modals/modals.actions.js";
|
|||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import { bodyshopHasDmsKey } from "../../utils/dmsUtils.js";
|
import { bodyshopHasDmsKey, DMS_MAP, getDmsMode } from "../../utils/dmsUtils.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -71,6 +72,11 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
|
|||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
|
||||||
const hasDMSKey = bodyshopHasDmsKey(bodyshop);
|
const hasDMSKey = bodyshopHasDmsKey(bodyshop);
|
||||||
|
const dmsMode = getDmsMode(bodyshop, "off");
|
||||||
|
const isReynoldsMode = dmsMode === DMS_MAP.reynolds;
|
||||||
|
const hasEarlyRO = !!(job?.dms_id && job?.dms_customer_id && job?.dms_advisor_id);
|
||||||
|
const canSendToDMS = !isReynoldsMode || hasEarlyRO;
|
||||||
|
const [showEarlyROModal, setShowEarlyROModal] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { Qb_Multi_Ar, ClosingPeriod }
|
treatments: { Qb_Multi_Ar, ClosingPeriod }
|
||||||
@@ -82,18 +88,18 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
|
|||||||
|
|
||||||
const handleFinish = async ({ removefromproduction, ...values }) => {
|
const handleFinish = async ({ removefromproduction, ...values }) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// Validate that all joblines have valid IDs
|
// Validate that all joblines have valid IDs
|
||||||
const joblinesWithIds = values.joblines.filter(jl => jl && jl.id);
|
const joblinesWithIds = values.joblines.filter((jl) => jl && jl.id);
|
||||||
if (joblinesWithIds.length !== values.joblines.length) {
|
if (joblinesWithIds.length !== values.joblines.length) {
|
||||||
notification.error({
|
notification.error({
|
||||||
title: t("jobs.errors.invalidjoblines"),
|
title: t("jobs.errors.invalidjoblines"),
|
||||||
message: t("jobs.errors.missingjoblineids")
|
description: t("jobs.errors.missingjoblineids")
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await client.mutate({
|
const result = await client.mutate({
|
||||||
mutation: generateJobLinesUpdatesForInvoicing(values.joblines)
|
mutation: generateJobLinesUpdatesForInvoicing(values.joblines)
|
||||||
});
|
});
|
||||||
@@ -208,9 +214,17 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
|
|||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
{bodyshopHasDmsKey(bodyshop) && (
|
{bodyshopHasDmsKey(bodyshop) && (
|
||||||
<Link to={`/manage/dms?jobId=${job.id}`}>
|
<>
|
||||||
<Button disabled={job.date_exported || !jobRO}>{t("jobs.actions.sendtodms")}</Button>
|
{canSendToDMS ? (
|
||||||
</Link>
|
<Link to={`/manage/dms?jobId=${job.id}`}>
|
||||||
|
<Button disabled={job.date_exported || !jobRO}>{t("jobs.actions.sendtodms")}</Button>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Button disabled={job.date_exported || !jobRO} onClick={() => setShowEarlyROModal(true)}>
|
||||||
|
{t("jobs.actions.sendtodms")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -426,13 +440,15 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select style={{ minWidth: "12rem" }} disabled={jobRO}>
|
<Select
|
||||||
{bodyshop.md_ins_cos.map((s) => (
|
style={{ minWidth: "12rem" }}
|
||||||
<Select.Option key={s.name} value={s.name}>
|
disabled={jobRO}
|
||||||
{s.name}
|
options={bodyshop.md_ins_cos.map((s) => ({
|
||||||
</Select.Option>
|
key: s.name,
|
||||||
))}
|
value: s.name,
|
||||||
</Select>
|
label: s.name
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -510,7 +526,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
|
|||||||
<Statistic
|
<Statistic
|
||||||
title={t("jobs.labels.pimraryamountpayable")}
|
title={t("jobs.labels.pimraryamountpayable")}
|
||||||
styles={{
|
styles={{
|
||||||
value: {
|
content: {
|
||||||
color: discrep.getAmount() >= 0 ? "green" : "red"
|
color: discrep.getAmount() >= 0 ? "green" : "red"
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -527,6 +543,30 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, set
|
|||||||
<Divider />
|
<Divider />
|
||||||
<JobsCloseLines job={job} />
|
<JobsCloseLines job={job} />
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
{/* Early RO Required Modal */}
|
||||||
|
<Modal
|
||||||
|
open={showEarlyROModal}
|
||||||
|
onCancel={() => setShowEarlyROModal(false)}
|
||||||
|
footer={null}
|
||||||
|
title={
|
||||||
|
<Space>
|
||||||
|
<Typography.Text type="warning" style={{ fontSize: "1.2em" }}>
|
||||||
|
⚠️
|
||||||
|
</Typography.Text>
|
||||||
|
<span>{t("dms.errors.earlyrorequired")}</span>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Space orientation="vertical" size="large" style={{ width: "100%" }}>
|
||||||
|
<Typography.Paragraph>{t("dms.errors.earlyrorequired.message")}</Typography.Paragraph>
|
||||||
|
<Link to={`/manage/jobs/${job.id}/admin`}>
|
||||||
|
<Button type="primary" block onClick={() => setShowEarlyROModal(false)}>
|
||||||
|
{t("general.actions.gotoadmin")}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</Space>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ import "./tech.page.styles.scss";
|
|||||||
import UpsellComponent, { upsellEnum } from "../../components/upsell/upsell.component.jsx";
|
import UpsellComponent, { upsellEnum } from "../../components/upsell/upsell.component.jsx";
|
||||||
import { lazyDev } from "../../utils/lazyWithPreload.jsx";
|
import { lazyDev } from "../../utils/lazyWithPreload.jsx";
|
||||||
|
|
||||||
const TimeTicketModalContainer = lazyDev(() => import("../../components/time-ticket-modal/time-ticket-modal.container"));
|
const NoteUpsertModal = lazyDev(() => import("../../components/note-upsert-modal/note-upsert-modal.container.jsx"));
|
||||||
|
const TimeTicketModalContainer = lazyDev(
|
||||||
|
() => import("../../components/time-ticket-modal/time-ticket-modal.container")
|
||||||
|
);
|
||||||
const EmailOverlayContainer = lazyDev(() => import("../../components/email-overlay/email-overlay.container.jsx"));
|
const EmailOverlayContainer = lazyDev(() => import("../../components/email-overlay/email-overlay.container.jsx"));
|
||||||
const PrintCenterModalContainer = lazyDev(
|
const PrintCenterModalContainer = lazyDev(
|
||||||
() => import("../../components/print-center-modal/print-center-modal.container")
|
() => import("../../components/print-center-modal/print-center-modal.container")
|
||||||
@@ -34,7 +37,9 @@ const TimeTicketModalTask = lazyDev(
|
|||||||
const TechAssignedProdJobs = lazyDev(() => import("../tech-assigned-prod-jobs/tech-assigned-prod-jobs.component"));
|
const TechAssignedProdJobs = lazyDev(() => import("../tech-assigned-prod-jobs/tech-assigned-prod-jobs.component"));
|
||||||
const TechDispatchedParts = lazyDev(() => import("../tech-dispatched-parts/tech-dispatched-parts.page"));
|
const TechDispatchedParts = lazyDev(() => import("../tech-dispatched-parts/tech-dispatched-parts.page"));
|
||||||
|
|
||||||
const TaskUpsertModalContainer = lazyDev(() => import("../../components/task-upsert-modal/task-upsert-modal.container"));
|
const TaskUpsertModalContainer = lazyDev(
|
||||||
|
() => import("../../components/task-upsert-modal/task-upsert-modal.container")
|
||||||
|
);
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
|
|
||||||
@@ -70,6 +75,8 @@ export function TechPage({ technician }) {
|
|||||||
<TechHeader />
|
<TechHeader />
|
||||||
|
|
||||||
<TaskUpsertModalContainer />
|
<TaskUpsertModalContainer />
|
||||||
|
<NoteUpsertModal />
|
||||||
|
|
||||||
<Content className="tech-content-container">
|
<Content className="tech-content-container">
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Suspense
|
<Suspense
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"arrivedon": "Arrived on: ",
|
"arrivedon": "Arrived on: ",
|
||||||
"arrivingjobs": "Arriving Jobs",
|
"arrivingjobs": "Arriving Jobs",
|
||||||
"blocked": "Blocked",
|
"blocked": "Blocked",
|
||||||
|
"bp": "B/P",
|
||||||
"cancelledappointment": "Canceled appointment for: ",
|
"cancelledappointment": "Canceled appointment for: ",
|
||||||
"completingjobs": "Completing Jobs",
|
"completingjobs": "Completing Jobs",
|
||||||
"dataconsistency": "<0>{{ro_number}}</0> has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.",
|
"dataconsistency": "<0>{{ro_number}}</0> has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.",
|
||||||
@@ -59,18 +60,17 @@
|
|||||||
"noarrivingjobs": "No Jobs are arriving.",
|
"noarrivingjobs": "No Jobs are arriving.",
|
||||||
"nocompletingjobs": "No Jobs scheduled for completion.",
|
"nocompletingjobs": "No Jobs scheduled for completion.",
|
||||||
"nodateselected": "No date has been selected.",
|
"nodateselected": "No date has been selected.",
|
||||||
|
"owner": "Owner",
|
||||||
"priorappointments": "Previous Appointments",
|
"priorappointments": "Previous Appointments",
|
||||||
"reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ",
|
"reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ",
|
||||||
|
"ro_number": "RO #",
|
||||||
|
"scheduled_completion": "Scheduled Completion",
|
||||||
"scheduledfor": "Scheduled appointment for: ",
|
"scheduledfor": "Scheduled appointment for: ",
|
||||||
"severalerrorsfound": "Several Jobs have issues which may prevent accurate smart scheduling. Click to expand.",
|
"severalerrorsfound": "Several Jobs have issues which may prevent accurate smart scheduling. Click to expand.",
|
||||||
"smartscheduling": "Smart Scheduling",
|
"smartscheduling": "Smart Scheduling",
|
||||||
"smspaymentreminder": "This is {{shopname}} reminding you about your remaining balance of {{amount}}. To pay for the said balance click the link {{payment_link}}.",
|
"smspaymentreminder": "This is {{shopname}} reminding you about your remaining balance of {{amount}}. To pay for the said balance click the link {{payment_link}}.",
|
||||||
"suggesteddates": "Suggested Dates",
|
"suggesteddates": "Suggested Dates",
|
||||||
"ro_number": "RO #",
|
"vehicle": "Vehicle"
|
||||||
"owner": "Owner",
|
|
||||||
"vehicle": "Vehicle",
|
|
||||||
"bp": "B/P",
|
|
||||||
"scheduled_completion": "Scheduled Completion"
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"canceled": "Appointment canceled successfully.",
|
"canceled": "Appointment canceled successfully.",
|
||||||
@@ -90,6 +90,11 @@
|
|||||||
"actions": "Actions"
|
"actions": "Actions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"audio": {
|
||||||
|
"manager": {
|
||||||
|
"description": "Click anywhere to enable the message ding."
|
||||||
|
}
|
||||||
|
},
|
||||||
"audit": {
|
"audit": {
|
||||||
"fields": {
|
"fields": {
|
||||||
"cc": "CC",
|
"cc": "CC",
|
||||||
@@ -149,11 +154,6 @@
|
|||||||
"tasks_updated": "Task '{{title}}' updated by {{updatedBy}}"
|
"tasks_updated": "Task '{{title}}' updated by {{updatedBy}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"audio": {
|
|
||||||
"manager": {
|
|
||||||
"description": "Click anywhere to enable the message ding."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"billlines": {
|
"billlines": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"newline": "New Line"
|
"newline": "New Line"
|
||||||
@@ -281,9 +281,9 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"creatingdefaultview": "Error creating default view.",
|
"creatingdefaultview": "Error creating default view.",
|
||||||
|
"duplicate_insurance_company": "Duplicate insurance company name. Each insurance company name must be unique",
|
||||||
"loading": "Unable to load shop details. Please call technical support.",
|
"loading": "Unable to load shop details. Please call technical support.",
|
||||||
"saving": "Error encountered while saving. {{message}}",
|
"saving": "Error encountered while saving. {{message}}"
|
||||||
"duplicate_insurance_company": "Duplicate insurance company name. Each insurance company name must be unique"
|
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
||||||
@@ -564,21 +564,18 @@
|
|||||||
"responsibilitycenter_tax_tier": "Tax {{typeNum}} Tier {{typeNumIterator}}",
|
"responsibilitycenter_tax_tier": "Tax {{typeNum}} Tier {{typeNumIterator}}",
|
||||||
"responsibilitycenter_tax_type": "Tax {{typeNum}} Type",
|
"responsibilitycenter_tax_type": "Tax {{typeNum}} Type",
|
||||||
"responsibilitycenters": {
|
"responsibilitycenters": {
|
||||||
"gogcode": "GOG Code (BreakOut)",
|
|
||||||
"item_type": "Item Type",
|
|
||||||
"item_type_gog": "GOG",
|
|
||||||
"item_type_paint": "Paint Materials",
|
|
||||||
"item_type_freight": "Freight",
|
|
||||||
"taxable_flag": "Taxable?",
|
|
||||||
"taxable": "Taxable",
|
|
||||||
"nontaxable": "Non-taxable",
|
|
||||||
"ap": "Accounts Payable",
|
"ap": "Accounts Payable",
|
||||||
"ar": "Accounts Receivable",
|
"ar": "Accounts Receivable",
|
||||||
"ats": "ATS",
|
"ats": "ATS",
|
||||||
"federal_tax": "Federal Tax",
|
"federal_tax": "Federal Tax",
|
||||||
"federal_tax_itc": "Federal Tax Credit",
|
"federal_tax_itc": "Federal Tax Credit",
|
||||||
|
"gogcode": "GOG Code (BreakOut)",
|
||||||
"gst_override": "GST Override Account #",
|
"gst_override": "GST Override Account #",
|
||||||
"invoiceexemptcode": "QuickBooks US - Invoice Tax Exempt Code",
|
"invoiceexemptcode": "QuickBooks US - Invoice Tax Exempt Code",
|
||||||
|
"item_type": "Item Type",
|
||||||
|
"item_type_freight": "Freight",
|
||||||
|
"item_type_gog": "GOG",
|
||||||
|
"item_type_paint": "Paint Materials",
|
||||||
"itemexemptcode": "QuickBooks US - Line Item Tax Exempt Code",
|
"itemexemptcode": "QuickBooks US - Line Item Tax Exempt Code",
|
||||||
"la1": "LA1",
|
"la1": "LA1",
|
||||||
"la2": "LA2",
|
"la2": "LA2",
|
||||||
@@ -597,6 +594,7 @@
|
|||||||
"local_tax": "Local Tax",
|
"local_tax": "Local Tax",
|
||||||
"mapa": "Paint Materials",
|
"mapa": "Paint Materials",
|
||||||
"mash": "Shop Materials",
|
"mash": "Shop Materials",
|
||||||
|
"nontaxable": "Non-taxable",
|
||||||
"paa": "Aftermarket",
|
"paa": "Aftermarket",
|
||||||
"pac": "Chrome",
|
"pac": "Chrome",
|
||||||
"pag": "Glass",
|
"pag": "Glass",
|
||||||
@@ -617,6 +615,8 @@
|
|||||||
"state": "State Tax Applies"
|
"state": "State Tax Applies"
|
||||||
},
|
},
|
||||||
"state_tax": "State Tax",
|
"state_tax": "State Tax",
|
||||||
|
"taxable": "Taxable",
|
||||||
|
"taxable_flag": "Taxable?",
|
||||||
"tow": "Towing"
|
"tow": "Towing"
|
||||||
},
|
},
|
||||||
"schedule_end_time": "Schedule Ending Time",
|
"schedule_end_time": "Schedule Ending Time",
|
||||||
@@ -678,8 +678,6 @@
|
|||||||
"zip_post": "Zip/Postal Code"
|
"zip_post": "Zip/Postal Code"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"parts_shop_management": "Shop Management",
|
|
||||||
"parts_vendor_management": "Vendor Management",
|
|
||||||
"2tiername": "Name => RO",
|
"2tiername": "Name => RO",
|
||||||
"2tiersetup": "2 Tier Setup",
|
"2tiersetup": "2 Tier Setup",
|
||||||
"2tiersource": "Source => RO",
|
"2tiersource": "Source => RO",
|
||||||
@@ -702,11 +700,11 @@
|
|||||||
"payers": "Payers"
|
"payers": "Payers"
|
||||||
},
|
},
|
||||||
"cdk_dealerid": "CDK Dealer ID",
|
"cdk_dealerid": "CDK Dealer ID",
|
||||||
"rr_dealerid": "Reynolds Store Number",
|
|
||||||
"costsmapping": "Costs Mapping",
|
"costsmapping": "Costs Mapping",
|
||||||
"dms_allocations": "DMS Allocations",
|
"dms_allocations": "DMS Allocations",
|
||||||
"pbs_serialnumber": "PBS Serial Number",
|
"pbs_serialnumber": "PBS Serial Number",
|
||||||
"profitsmapping": "Profits Mapping",
|
"profitsmapping": "Profits Mapping",
|
||||||
|
"rr_dealerid": "Reynolds Store Number",
|
||||||
"title": "DMS"
|
"title": "DMS"
|
||||||
},
|
},
|
||||||
"emaillater": "Email Later",
|
"emaillater": "Email Later",
|
||||||
@@ -733,6 +731,8 @@
|
|||||||
"followers": "Notifications"
|
"followers": "Notifications"
|
||||||
},
|
},
|
||||||
"orderstatuses": "Order Statuses",
|
"orderstatuses": "Order Statuses",
|
||||||
|
"parts_shop_management": "Shop Management",
|
||||||
|
"parts_vendor_management": "Vendor Management",
|
||||||
"partslocations": "Parts Locations",
|
"partslocations": "Parts Locations",
|
||||||
"partsscan": "Parts Scanning",
|
"partsscan": "Parts Scanning",
|
||||||
"printlater": "Print Later",
|
"printlater": "Print Later",
|
||||||
@@ -1047,7 +1047,9 @@
|
|||||||
},
|
},
|
||||||
"dms": {
|
"dms": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"alreadyexported": "This job has already been sent to the DMS. If you need to resend it, please use admin permissions to mark the job for re-export."
|
"alreadyexported": "This job has already been sent to the DMS. If you need to resend it, please use admin permissions to mark the job for re-export.",
|
||||||
|
"earlyrorequired": "Early RO Required",
|
||||||
|
"earlyrorequired.message": "This job requires an early Repair Order to be created before posting to Reynolds. Please use the admin panel to create the early RO first."
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"refreshallocations": "Refresh to see DMS Allocations."
|
"refreshallocations": "Refresh to see DMS Allocations."
|
||||||
@@ -1228,8 +1230,6 @@
|
|||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"select": "Select",
|
|
||||||
"optional": "Optional",
|
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"autoupdate": "{{app}} will automatically update in {{time}} seconds. Please save all changes.",
|
"autoupdate": "{{app}} will automatically update in {{time}} seconds. Please save all changes.",
|
||||||
"calculate": "Calculate",
|
"calculate": "Calculate",
|
||||||
@@ -1246,9 +1246,11 @@
|
|||||||
"deselectall": "Deselect All",
|
"deselectall": "Deselect All",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
|
"gotoadmin": "Go to Admin Panel",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"ok": "Ok",
|
"ok": "Ok",
|
||||||
|
"optional": "Optional",
|
||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
"print": "Print",
|
"print": "Print",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
@@ -1259,6 +1261,7 @@
|
|||||||
"save": "Save",
|
"save": "Save",
|
||||||
"saveandnew": "Save and New",
|
"saveandnew": "Save and New",
|
||||||
"saveas": "Save As",
|
"saveas": "Save As",
|
||||||
|
"select": "Select",
|
||||||
"selectall": "Select All",
|
"selectall": "Select All",
|
||||||
"send": "Send",
|
"send": "Send",
|
||||||
"sendbysms": "Send by SMS",
|
"sendbysms": "Send by SMS",
|
||||||
@@ -1288,8 +1291,7 @@
|
|||||||
"vehicle": "Vehicle"
|
"vehicle": "Vehicle"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"selected": "Selected",
|
"apply": "Apply",
|
||||||
"settings": "Settings",
|
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"areyousure": "Are you sure?",
|
"areyousure": "Are you sure?",
|
||||||
"barcode": "Barcode",
|
"barcode": "Barcode",
|
||||||
@@ -1343,8 +1345,10 @@
|
|||||||
"search": "Search...",
|
"search": "Search...",
|
||||||
"searchresults": "Results for {{search}}",
|
"searchresults": "Results for {{search}}",
|
||||||
"selectdate": "Select date...",
|
"selectdate": "Select date...",
|
||||||
|
"selected": "Selected",
|
||||||
"sendagain": "Send Again",
|
"sendagain": "Send Again",
|
||||||
"sendby": "Send By",
|
"sendby": "Send By",
|
||||||
|
"settings": "Settings",
|
||||||
"signin": "Sign In",
|
"signin": "Sign In",
|
||||||
"sms": "SMS",
|
"sms": "SMS",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
@@ -1587,13 +1591,13 @@
|
|||||||
"labels": {
|
"labels": {
|
||||||
"adjustmenttobeadded": "Adjustment to be added: {{adjustment}}",
|
"adjustmenttobeadded": "Adjustment to be added: {{adjustment}}",
|
||||||
"billref": "Latest Bill",
|
"billref": "Latest Bill",
|
||||||
|
"bulk_location_help": "This will set the same location on all selected lines.",
|
||||||
"convertedtolabor": "This line has been converted to labor. Ensure you adjust the profit center for the amount accordingly.",
|
"convertedtolabor": "This line has been converted to labor. Ensure you adjust the profit center for the amount accordingly.",
|
||||||
"edit": "Edit Line",
|
"edit": "Edit Line",
|
||||||
"ioucreated": "IOU",
|
"ioucreated": "IOU",
|
||||||
"new": "New Line",
|
"new": "New Line",
|
||||||
"nostatus": "No Status",
|
"nostatus": "No Status",
|
||||||
"presets": "Jobline Presets",
|
"presets": "Jobline Presets"
|
||||||
"bulk_location_help": "This will set the same location on all selected lines."
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"created": "Job line created successfully.",
|
"created": "Job line created successfully.",
|
||||||
@@ -1621,11 +1625,13 @@
|
|||||||
"changestatus": "Change Status",
|
"changestatus": "Change Status",
|
||||||
"changestimator": "Change Estimator",
|
"changestimator": "Change Estimator",
|
||||||
"convert": "Convert",
|
"convert": "Convert",
|
||||||
|
"convertwithoutearlyro": "Convert without Early RO",
|
||||||
"createiou": "Create IOU",
|
"createiou": "Create IOU",
|
||||||
"deliver": "Deliver",
|
"deliver": "Deliver",
|
||||||
"deliver_quick": "Quick Deliver",
|
"deliver_quick": "Quick Deliver",
|
||||||
"dms": {
|
"dms": {
|
||||||
"addpayer": "Add Payer",
|
"addpayer": "Add Payer",
|
||||||
|
"createearlyro": "Create RR RO",
|
||||||
"createnewcustomer": "Create New Customer",
|
"createnewcustomer": "Create New Customer",
|
||||||
"findmakemodelcode": "Find Make/Model Code",
|
"findmakemodelcode": "Find Make/Model Code",
|
||||||
"getmakes": "Get Makes",
|
"getmakes": "Get Makes",
|
||||||
@@ -1634,6 +1640,7 @@
|
|||||||
},
|
},
|
||||||
"post": "Post",
|
"post": "Post",
|
||||||
"refetchmakesmodels": "Refetch Make and Model Codes",
|
"refetchmakesmodels": "Refetch Make and Model Codes",
|
||||||
|
"update_ro": "Update RO",
|
||||||
"usegeneric": "Use Generic Customer",
|
"usegeneric": "Use Generic Customer",
|
||||||
"useselected": "Use Selected Customer"
|
"useselected": "Use Selected Customer"
|
||||||
},
|
},
|
||||||
@@ -1701,9 +1708,9 @@
|
|||||||
"actual_delivery": "Actual Delivery",
|
"actual_delivery": "Actual Delivery",
|
||||||
"actual_in": "Actual In",
|
"actual_in": "Actual In",
|
||||||
"acv_amount": "ACV Amount",
|
"acv_amount": "ACV Amount",
|
||||||
"admin_clerk": "Admin Clerk",
|
|
||||||
"adjustment_bottom_line": "Adjustments",
|
"adjustment_bottom_line": "Adjustments",
|
||||||
"adjustmenthours": "Adjustment Hours",
|
"adjustmenthours": "Adjustment Hours",
|
||||||
|
"admin_clerk": "Admin Clerk",
|
||||||
"alt_transport": "Alt. Trans.",
|
"alt_transport": "Alt. Trans.",
|
||||||
"area_of_damage_impact": {
|
"area_of_damage_impact": {
|
||||||
"10": "Left Front Side",
|
"10": "Left Front Side",
|
||||||
@@ -1784,9 +1791,8 @@
|
|||||||
"ded_status": "Deductible Status",
|
"ded_status": "Deductible Status",
|
||||||
"depreciation_taxes": "Betterment/Depreciation/Taxes",
|
"depreciation_taxes": "Betterment/Depreciation/Taxes",
|
||||||
"dms": {
|
"dms": {
|
||||||
"first_name": "First Name",
|
|
||||||
"last_name": "Last Name",
|
|
||||||
"address": "Customer Address",
|
"address": "Customer Address",
|
||||||
|
"advisor": "Advisor #",
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"center": "Center",
|
"center": "Center",
|
||||||
"control_type": {
|
"control_type": {
|
||||||
@@ -1794,17 +1800,19 @@
|
|||||||
},
|
},
|
||||||
"cost": "Cost",
|
"cost": "Cost",
|
||||||
"cost_dms_acctnumber": "Cost DMS Acct #",
|
"cost_dms_acctnumber": "Cost DMS Acct #",
|
||||||
|
"customer": "Customer #",
|
||||||
"dms_make": "DMS Make",
|
"dms_make": "DMS Make",
|
||||||
"dms_model": "DMS Model",
|
"dms_model": "DMS Model",
|
||||||
"dms_model_override": "Override DMS Make/Model",
|
"dms_model_override": "Override DMS Make/Model",
|
||||||
"dms_unsold": "New, Unsold Vehicle",
|
"dms_unsold": "New, Unsold Vehicle",
|
||||||
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
|
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
|
||||||
|
"first_name": "First Name",
|
||||||
"id": "DMS ID",
|
"id": "DMS ID",
|
||||||
"inservicedate": "In Service Date",
|
"inservicedate": "In Service Date",
|
||||||
"journal": "Journal #",
|
"journal": "Journal #",
|
||||||
"make_override": "Make Override",
|
"last_name": "Last Name",
|
||||||
"advisor": "Advisor #",
|
|
||||||
"lines": "Posting Lines",
|
"lines": "Posting Lines",
|
||||||
|
"make_override": "Make Override",
|
||||||
"name1": "Customer Name",
|
"name1": "Customer Name",
|
||||||
"payer": {
|
"payer": {
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
@@ -1817,7 +1825,11 @@
|
|||||||
"sale": "Sale",
|
"sale": "Sale",
|
||||||
"sale_dms_acctnumber": "Sale DMS Acct #",
|
"sale_dms_acctnumber": "Sale DMS Acct #",
|
||||||
"story": "Story",
|
"story": "Story",
|
||||||
"vinowner": "VIN Owner"
|
"vinowner": "VIN Owner",
|
||||||
|
"rr_opcode": "RR OpCode",
|
||||||
|
"rr_opcode_prefix": "Prefix",
|
||||||
|
"rr_opcode_suffix": "Suffix",
|
||||||
|
"rr_opcode_base": "Base"
|
||||||
},
|
},
|
||||||
"dms_allocation": "DMS Allocation",
|
"dms_allocation": "DMS Allocation",
|
||||||
"driveable": "Driveable",
|
"driveable": "Driveable",
|
||||||
@@ -1945,7 +1957,7 @@
|
|||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"name": "Name"
|
"name": "Name"
|
||||||
},
|
},
|
||||||
"queued_for_parts": "Queued for Parts",
|
"queued_for_parts": "Queued",
|
||||||
"rate_ats": "ATS Rate",
|
"rate_ats": "ATS Rate",
|
||||||
"rate_ats_flat": "ATS Flat Rate",
|
"rate_ats_flat": "ATS Flat Rate",
|
||||||
"rate_la1": "LA1",
|
"rate_la1": "LA1",
|
||||||
@@ -2102,6 +2114,11 @@
|
|||||||
"damageto": "Damage to $t(jobs.fields.area_of_damage_impact.{{area_of_damage}}).",
|
"damageto": "Damage to $t(jobs.fields.area_of_damage_impact.{{area_of_damage}}).",
|
||||||
"defaultstory": "B/S RO: {{ro_number}}. Owner: {{ownr_nm}}. Insurance Co: {{ins_co_nm}}. Claim/PO #: {{clm_po}}",
|
"defaultstory": "B/S RO: {{ro_number}}. Owner: {{ownr_nm}}. Insurance Co: {{ins_co_nm}}. Claim/PO #: {{clm_po}}",
|
||||||
"disablebillwip": "Cost and WIP for bills has been ignored per shop configuration.",
|
"disablebillwip": "Cost and WIP for bills has been ignored per shop configuration.",
|
||||||
|
"earlyro": {
|
||||||
|
"created": "Early RO Created:",
|
||||||
|
"fields": "Required fields:",
|
||||||
|
"willupdate": "This will update the existing RO with full job data."
|
||||||
|
},
|
||||||
"invoicedatefuture": "Invoice date must be today or in the future for CDK posting.",
|
"invoicedatefuture": "Invoice date must be today or in the future for CDK posting.",
|
||||||
"kmoutnotgreaterthankmin": "Mileage out must be greater than mileage in.",
|
"kmoutnotgreaterthankmin": "Mileage out must be greater than mileage in.",
|
||||||
"logs": "Logs",
|
"logs": "Logs",
|
||||||
@@ -2259,6 +2276,7 @@
|
|||||||
"delete": "Job deleted successfully.",
|
"delete": "Job deleted successfully.",
|
||||||
"deleted": "Job deleted successfully.",
|
"deleted": "Job deleted successfully.",
|
||||||
"duplicated": "Job duplicated successfully. ",
|
"duplicated": "Job duplicated successfully. ",
|
||||||
|
"early_ro_created": "Early RO Created",
|
||||||
"exported": "Job(s) exported successfully. ",
|
"exported": "Job(s) exported successfully. ",
|
||||||
"invoiced": "Job closed and invoiced successfully.",
|
"invoiced": "Job closed and invoiced successfully.",
|
||||||
"ioucreated": "IOU created successfully. Click to see.",
|
"ioucreated": "IOU created successfully. Click to see.",
|
||||||
@@ -2447,6 +2465,7 @@
|
|||||||
"labels": {
|
"labels": {
|
||||||
"addlabel": "Add a label to this conversation.",
|
"addlabel": "Add a label to this conversation.",
|
||||||
"archive": "Archive",
|
"archive": "Archive",
|
||||||
|
"mark_unread": "Mark as unread",
|
||||||
"maxtenimages": "You can only select up to a maximum of 10 images at a time.",
|
"maxtenimages": "You can only select up to a maximum of 10 images at a time.",
|
||||||
"messaging": "Messaging",
|
"messaging": "Messaging",
|
||||||
"no_consent": "Opted-out",
|
"no_consent": "Opted-out",
|
||||||
@@ -2459,8 +2478,7 @@
|
|||||||
"selectmedia": "Select Media",
|
"selectmedia": "Select Media",
|
||||||
"sentby": "Sent by {{by}} at {{time}}",
|
"sentby": "Sent by {{by}} at {{time}}",
|
||||||
"typeamessage": "Send a message...",
|
"typeamessage": "Send a message...",
|
||||||
"unarchive": "Unarchive",
|
"unarchive": "Unarchive"
|
||||||
"mark_unread": "Mark as unread"
|
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"conversation_list": "Conversation List"
|
"conversation_list": "Conversation List"
|
||||||
@@ -2614,20 +2632,20 @@
|
|||||||
"name": "Owner Details"
|
"name": "Owner Details"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"cell": "Cell",
|
||||||
"create_new": "Create a new owner record.",
|
"create_new": "Create a new owner record.",
|
||||||
"deleteconfirm": "Are you sure you want to delete this owner? This cannot be undone.",
|
"deleteconfirm": "Are you sure you want to delete this owner? This cannot be undone.",
|
||||||
|
"email": "Email",
|
||||||
"existing_owners": "Existing Owners",
|
"existing_owners": "Existing Owners",
|
||||||
"fromclaim": "Current Claim",
|
"fromclaim": "Current Claim",
|
||||||
"fromowner": "Historical Owner Record",
|
"fromowner": "Historical Owner Record",
|
||||||
"relatedjobs": "Related Jobs",
|
|
||||||
"updateowner": "Update Owner",
|
|
||||||
"work": "Work",
|
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"cell": "Cell",
|
|
||||||
"other": "Other",
|
"other": "Other",
|
||||||
"email": "Email",
|
|
||||||
"phone": "Phone",
|
"phone": "Phone",
|
||||||
"sms": "SMS"
|
"relatedjobs": "Related Jobs",
|
||||||
|
"sms": "SMS",
|
||||||
|
"updateowner": "Update Owner",
|
||||||
|
"work": "Work"
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"delete": "Owner deleted successfully.",
|
"delete": "Owner deleted successfully.",
|
||||||
@@ -2638,6 +2656,10 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"order": "Order Parts",
|
"order": "Order Parts",
|
||||||
"orderinhouse": "Order as In House"
|
"orderinhouse": "Order as In House"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"view_counts_only": "View Parts Counts Only",
|
||||||
|
"view_timestamps": "Show timestamps"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parts_dispatch": {
|
"parts_dispatch": {
|
||||||
@@ -2987,8 +3009,6 @@
|
|||||||
"settings": "Error saving board settings: {{error}}"
|
"settings": "Error saving board settings: {{error}}"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"click_for_statuses": "Click to view parts statuses",
|
|
||||||
"partsreceived": "Parts Received",
|
|
||||||
"actual_in": "Actual In",
|
"actual_in": "Actual In",
|
||||||
"addnewprofile": "Add New Profile",
|
"addnewprofile": "Add New Profile",
|
||||||
"alert": "Alert",
|
"alert": "Alert",
|
||||||
@@ -3007,6 +3027,7 @@
|
|||||||
"card_size": "Card Size",
|
"card_size": "Card Size",
|
||||||
"cardcolor": "Colored Cards",
|
"cardcolor": "Colored Cards",
|
||||||
"cardsettings": "Card Settings",
|
"cardsettings": "Card Settings",
|
||||||
|
"click_for_statuses": "Click to view parts statuses",
|
||||||
"clm_no": "Claim Number",
|
"clm_no": "Claim Number",
|
||||||
"comment": "Comment",
|
"comment": "Comment",
|
||||||
"compact": "Compact Cards",
|
"compact": "Compact Cards",
|
||||||
@@ -3027,6 +3048,7 @@
|
|||||||
"orientation": "Board Orientation",
|
"orientation": "Board Orientation",
|
||||||
"ownr_nm": "Customer Name",
|
"ownr_nm": "Customer Name",
|
||||||
"paintpriority": "P/P",
|
"paintpriority": "P/P",
|
||||||
|
"partsreceived": "Parts Received",
|
||||||
"partsstatus": "Parts Status",
|
"partsstatus": "Parts Status",
|
||||||
"production_note": "Production Note",
|
"production_note": "Production Note",
|
||||||
"refinishhours": "R",
|
"refinishhours": "R",
|
||||||
@@ -3573,17 +3595,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
"parts_settings": "Parts Management Settings | {{app}}",
|
|
||||||
"simplified-parts-jobs": "Parts Management | {{app}}",
|
|
||||||
"accounting-payables": "Payables | {{app}}",
|
"accounting-payables": "Payables | {{app}}",
|
||||||
"accounting-payments": "Payments | {{app}}",
|
"accounting-payments": "Payments | {{app}}",
|
||||||
"accounting-receivables": "Receivables | {{app}}",
|
"accounting-receivables": "Receivables | {{app}}",
|
||||||
"all_tasks": "All Tasks | {{app}}",
|
"all_tasks": "All Tasks | {{app}}",
|
||||||
"app": "",
|
"app": "",
|
||||||
"bc": {
|
"bc": {
|
||||||
"simplified-parts-jobs": "Jobs",
|
|
||||||
"parts": "Parts",
|
|
||||||
"parts_settings": "Settings",
|
|
||||||
"accounting-payables": "Payables",
|
"accounting-payables": "Payables",
|
||||||
"accounting-payments": "Payments",
|
"accounting-payments": "Payments",
|
||||||
"accounting-receivables": "Receivables",
|
"accounting-receivables": "Receivables",
|
||||||
@@ -3615,7 +3632,9 @@
|
|||||||
"my_tasks": "My Tasks",
|
"my_tasks": "My Tasks",
|
||||||
"owner-detail": "{{name}}",
|
"owner-detail": "{{name}}",
|
||||||
"owners": "Owners",
|
"owners": "Owners",
|
||||||
|
"parts": "Parts",
|
||||||
"parts-queue": "Parts Queue",
|
"parts-queue": "Parts Queue",
|
||||||
|
"parts_settings": "Settings",
|
||||||
"payments-all": "All Payments",
|
"payments-all": "All Payments",
|
||||||
"phonebook": "Phonebook",
|
"phonebook": "Phonebook",
|
||||||
"productionboard": "Production Board - Visual",
|
"productionboard": "Production Board - Visual",
|
||||||
@@ -3627,6 +3646,7 @@
|
|||||||
"shop-csi": "CSI Responses",
|
"shop-csi": "CSI Responses",
|
||||||
"shop-templates": "Shop Templates",
|
"shop-templates": "Shop Templates",
|
||||||
"shop-vendors": "Vendors",
|
"shop-vendors": "Vendors",
|
||||||
|
"simplified-parts-jobs": "Jobs",
|
||||||
"tasks": "Tasks",
|
"tasks": "Tasks",
|
||||||
"temporarydocs": "Temporary Documents",
|
"temporarydocs": "Temporary Documents",
|
||||||
"timetickets": "Time Tickets",
|
"timetickets": "Time Tickets",
|
||||||
@@ -3662,7 +3682,9 @@
|
|||||||
"my_tasks": "My Tasks | {{app}}",
|
"my_tasks": "My Tasks | {{app}}",
|
||||||
"owners": "All Owners | {{app}}",
|
"owners": "All Owners | {{app}}",
|
||||||
"owners-detail": "{{name}} | {{app}}",
|
"owners-detail": "{{name}} | {{app}}",
|
||||||
|
"parts": "",
|
||||||
"parts-queue": "Parts Queue | {{app}}",
|
"parts-queue": "Parts Queue | {{app}}",
|
||||||
|
"parts_settings": "Parts Management Settings | {{app}}",
|
||||||
"payments-all": "Payments | {{app}}",
|
"payments-all": "Payments | {{app}}",
|
||||||
"phonebook": "Phonebook | {{app}}",
|
"phonebook": "Phonebook | {{app}}",
|
||||||
"productionboard": "Production Board - Visual | {{app}}",
|
"productionboard": "Production Board - Visual | {{app}}",
|
||||||
@@ -3678,6 +3700,7 @@
|
|||||||
"shop-csi": "CSI Responses | {{app}}",
|
"shop-csi": "CSI Responses | {{app}}",
|
||||||
"shop-templates": "Shop Templates | {{app}}",
|
"shop-templates": "Shop Templates | {{app}}",
|
||||||
"shop_vendors": "Vendors | {{app}}",
|
"shop_vendors": "Vendors | {{app}}",
|
||||||
|
"simplified-parts-jobs": "Parts Management | {{app}}",
|
||||||
"tasks": "Tasks",
|
"tasks": "Tasks",
|
||||||
"techconsole": "Technician Console | {{app}}",
|
"techconsole": "Technician Console | {{app}}",
|
||||||
"techjobclock": "Technician Job Clock | {{app}}",
|
"techjobclock": "Technician Job Clock | {{app}}",
|
||||||
@@ -3838,10 +3861,10 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"changepassword": "Change Password",
|
"changepassword": "Change Password",
|
||||||
"signout": "Sign Out",
|
"dark_theme": "Switch to Dark Theme",
|
||||||
"updateprofile": "Update Profile",
|
|
||||||
"light_theme": "Switch to Light Theme",
|
"light_theme": "Switch to Light Theme",
|
||||||
"dark_theme": "Switch to Dark Theme"
|
"signout": "Sign Out",
|
||||||
|
"updateprofile": "Update Profile"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"updating": "Error updating user or association {{message}}"
|
"updating": "Error updating user or association {{message}}"
|
||||||
@@ -3855,14 +3878,14 @@
|
|||||||
"labels": {
|
"labels": {
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"changepassword": "Change Password",
|
"changepassword": "Change Password",
|
||||||
"profileinfo": "Profile Info",
|
|
||||||
"user_settings": "User Settings",
|
|
||||||
"play_sound_for_new_messages": "Play a sound for new messages",
|
|
||||||
"notification_sound_on": "Sound is ON",
|
|
||||||
"notification_sound_off": "Sound is OFF",
|
|
||||||
"notification_sound_enabled": "Notification sound enabled",
|
|
||||||
"notification_sound_disabled": "Notification sound disabled",
|
"notification_sound_disabled": "Notification sound disabled",
|
||||||
"notification_sound_help": "Toggle the ding for incoming chat messages."
|
"notification_sound_enabled": "Notification sound enabled",
|
||||||
|
"notification_sound_help": "Toggle the ding for incoming chat messages.",
|
||||||
|
"notification_sound_off": "Sound is OFF",
|
||||||
|
"notification_sound_on": "Sound is ON",
|
||||||
|
"play_sound_for_new_messages": "Play a sound for new messages",
|
||||||
|
"profileinfo": "Profile Info",
|
||||||
|
"user_settings": "User Settings"
|
||||||
},
|
},
|
||||||
"successess": {
|
"successess": {
|
||||||
"passwordchanged": "Password changed successfully. "
|
"passwordchanged": "Password changed successfully. "
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"arrivedon": "Llegado el:",
|
"arrivedon": "Llegado el:",
|
||||||
"arrivingjobs": "",
|
"arrivingjobs": "",
|
||||||
"blocked": "",
|
"blocked": "",
|
||||||
|
"bp": "",
|
||||||
"cancelledappointment": "Cita cancelada para:",
|
"cancelledappointment": "Cita cancelada para:",
|
||||||
"completingjobs": "",
|
"completingjobs": "",
|
||||||
"dataconsistency": "",
|
"dataconsistency": "",
|
||||||
@@ -59,18 +60,17 @@
|
|||||||
"noarrivingjobs": "",
|
"noarrivingjobs": "",
|
||||||
"nocompletingjobs": "",
|
"nocompletingjobs": "",
|
||||||
"nodateselected": "No se ha seleccionado ninguna fecha.",
|
"nodateselected": "No se ha seleccionado ninguna fecha.",
|
||||||
|
"owner": "",
|
||||||
"priorappointments": "Nombramientos previos",
|
"priorappointments": "Nombramientos previos",
|
||||||
"reminder": "",
|
"reminder": "",
|
||||||
|
"ro_number": "",
|
||||||
|
"scheduled_completion": "",
|
||||||
"scheduledfor": "Cita programada para:",
|
"scheduledfor": "Cita programada para:",
|
||||||
"severalerrorsfound": "",
|
"severalerrorsfound": "",
|
||||||
"smartscheduling": "",
|
"smartscheduling": "",
|
||||||
"smspaymentreminder": "",
|
"smspaymentreminder": "",
|
||||||
"suggesteddates": "",
|
"suggesteddates": "",
|
||||||
"ro_number": "",
|
"vehicle": ""
|
||||||
"owner": "",
|
|
||||||
"vehicle": "",
|
|
||||||
"bp": "",
|
|
||||||
"scheduled_completion": ""
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"canceled": "Cita cancelada con éxito.",
|
"canceled": "Cita cancelada con éxito.",
|
||||||
@@ -90,6 +90,11 @@
|
|||||||
"actions": "Comportamiento"
|
"actions": "Comportamiento"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"audio": {
|
||||||
|
"manager": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"audit": {
|
"audit": {
|
||||||
"fields": {
|
"fields": {
|
||||||
"cc": "",
|
"cc": "",
|
||||||
@@ -149,11 +154,6 @@
|
|||||||
"tasks_updated": ""
|
"tasks_updated": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"audio": {
|
|
||||||
"manager": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"billlines": {
|
"billlines": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"newline": ""
|
"newline": ""
|
||||||
@@ -281,9 +281,9 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"creatingdefaultview": "",
|
"creatingdefaultview": "",
|
||||||
|
"duplicate_insurance_company": "",
|
||||||
"loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico.",
|
"loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico.",
|
||||||
"saving": "",
|
"saving": ""
|
||||||
"duplicate_insurance_company": ""
|
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"ReceivableCustomField": "",
|
"ReceivableCustomField": "",
|
||||||
@@ -564,21 +564,18 @@
|
|||||||
"responsibilitycenter_tax_tier": "",
|
"responsibilitycenter_tax_tier": "",
|
||||||
"responsibilitycenter_tax_type": "",
|
"responsibilitycenter_tax_type": "",
|
||||||
"responsibilitycenters": {
|
"responsibilitycenters": {
|
||||||
"gogcode": "",
|
|
||||||
"item_type": "Item Type",
|
|
||||||
"item_type_gog": "",
|
|
||||||
"item_type_paint": "",
|
|
||||||
"item_type_freight": "",
|
|
||||||
"taxable_flag": "",
|
|
||||||
"taxable": "",
|
|
||||||
"nontaxable": "",
|
|
||||||
"ap": "",
|
"ap": "",
|
||||||
"ar": "",
|
"ar": "",
|
||||||
"ats": "",
|
"ats": "",
|
||||||
"federal_tax": "",
|
"federal_tax": "",
|
||||||
"federal_tax_itc": "",
|
"federal_tax_itc": "",
|
||||||
|
"gogcode": "",
|
||||||
"gst_override": "",
|
"gst_override": "",
|
||||||
"invoiceexemptcode": "",
|
"invoiceexemptcode": "",
|
||||||
|
"item_type": "Item Type",
|
||||||
|
"item_type_freight": "",
|
||||||
|
"item_type_gog": "",
|
||||||
|
"item_type_paint": "",
|
||||||
"itemexemptcode": "",
|
"itemexemptcode": "",
|
||||||
"la1": "",
|
"la1": "",
|
||||||
"la2": "",
|
"la2": "",
|
||||||
@@ -597,6 +594,7 @@
|
|||||||
"local_tax": "",
|
"local_tax": "",
|
||||||
"mapa": "",
|
"mapa": "",
|
||||||
"mash": "",
|
"mash": "",
|
||||||
|
"nontaxable": "",
|
||||||
"paa": "",
|
"paa": "",
|
||||||
"pac": "",
|
"pac": "",
|
||||||
"pag": "",
|
"pag": "",
|
||||||
@@ -617,6 +615,8 @@
|
|||||||
"state": ""
|
"state": ""
|
||||||
},
|
},
|
||||||
"state_tax": "",
|
"state_tax": "",
|
||||||
|
"taxable": "",
|
||||||
|
"taxable_flag": "",
|
||||||
"tow": ""
|
"tow": ""
|
||||||
},
|
},
|
||||||
"schedule_end_time": "",
|
"schedule_end_time": "",
|
||||||
@@ -678,8 +678,6 @@
|
|||||||
"zip_post": ""
|
"zip_post": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"parts_shop_management": "",
|
|
||||||
"parts_vendor_management": "",
|
|
||||||
"2tiername": "",
|
"2tiername": "",
|
||||||
"2tiersetup": "",
|
"2tiersetup": "",
|
||||||
"2tiersource": "",
|
"2tiersource": "",
|
||||||
@@ -702,11 +700,11 @@
|
|||||||
"payers": ""
|
"payers": ""
|
||||||
},
|
},
|
||||||
"cdk_dealerid": "",
|
"cdk_dealerid": "",
|
||||||
"rr_dealerid": "",
|
|
||||||
"costsmapping": "",
|
"costsmapping": "",
|
||||||
"dms_allocations": "",
|
"dms_allocations": "",
|
||||||
"pbs_serialnumber": "",
|
"pbs_serialnumber": "",
|
||||||
"profitsmapping": "",
|
"profitsmapping": "",
|
||||||
|
"rr_dealerid": "",
|
||||||
"title": ""
|
"title": ""
|
||||||
},
|
},
|
||||||
"emaillater": "",
|
"emaillater": "",
|
||||||
@@ -733,6 +731,8 @@
|
|||||||
"followers": ""
|
"followers": ""
|
||||||
},
|
},
|
||||||
"orderstatuses": "",
|
"orderstatuses": "",
|
||||||
|
"parts_shop_management": "",
|
||||||
|
"parts_vendor_management": "",
|
||||||
"partslocations": "",
|
"partslocations": "",
|
||||||
"partsscan": "",
|
"partsscan": "",
|
||||||
"printlater": "",
|
"printlater": "",
|
||||||
@@ -1047,7 +1047,9 @@
|
|||||||
},
|
},
|
||||||
"dms": {
|
"dms": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"alreadyexported": ""
|
"alreadyexported": "",
|
||||||
|
"earlyrorequired": "",
|
||||||
|
"earlyrorequired.message": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"refreshallocations": ""
|
"refreshallocations": ""
|
||||||
@@ -1244,9 +1246,11 @@
|
|||||||
"deselectall": "",
|
"deselectall": "",
|
||||||
"download": "",
|
"download": "",
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
|
"gotoadmin": "",
|
||||||
"login": "",
|
"login": "",
|
||||||
"next": "",
|
"next": "",
|
||||||
"ok": "",
|
"ok": "",
|
||||||
|
"optional": "",
|
||||||
"previous": "",
|
"previous": "",
|
||||||
"print": "",
|
"print": "",
|
||||||
"refresh": "",
|
"refresh": "",
|
||||||
@@ -1257,6 +1261,7 @@
|
|||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
"saveandnew": "",
|
"saveandnew": "",
|
||||||
"saveas": "",
|
"saveas": "",
|
||||||
|
"select": "",
|
||||||
"selectall": "",
|
"selectall": "",
|
||||||
"send": "",
|
"send": "",
|
||||||
"sendbysms": "",
|
"sendbysms": "",
|
||||||
@@ -1286,9 +1291,8 @@
|
|||||||
"vehicle": ""
|
"vehicle": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"selected": "",
|
"apply": "",
|
||||||
"actions": "Comportamiento",
|
"actions": "Comportamiento",
|
||||||
"settings": "",
|
|
||||||
"areyousure": "",
|
"areyousure": "",
|
||||||
"barcode": "código de barras",
|
"barcode": "código de barras",
|
||||||
"cancel": "",
|
"cancel": "",
|
||||||
@@ -1341,8 +1345,10 @@
|
|||||||
"search": "Buscar...",
|
"search": "Buscar...",
|
||||||
"searchresults": "",
|
"searchresults": "",
|
||||||
"selectdate": "",
|
"selectdate": "",
|
||||||
|
"selected": "",
|
||||||
"sendagain": "",
|
"sendagain": "",
|
||||||
"sendby": "",
|
"sendby": "",
|
||||||
|
"settings": "",
|
||||||
"signin": "",
|
"signin": "",
|
||||||
"sms": "",
|
"sms": "",
|
||||||
"status": "",
|
"status": "",
|
||||||
@@ -1585,13 +1591,13 @@
|
|||||||
"labels": {
|
"labels": {
|
||||||
"adjustmenttobeadded": "",
|
"adjustmenttobeadded": "",
|
||||||
"billref": "",
|
"billref": "",
|
||||||
|
"bulk_location_help": "",
|
||||||
"convertedtolabor": "",
|
"convertedtolabor": "",
|
||||||
"edit": "Línea de edición",
|
"edit": "Línea de edición",
|
||||||
"ioucreated": "",
|
"ioucreated": "",
|
||||||
"new": "Nueva línea",
|
"new": "Nueva línea",
|
||||||
"nostatus": "",
|
"nostatus": "",
|
||||||
"presets": "",
|
"presets": ""
|
||||||
"bulk_location_help": ""
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"created": "",
|
"created": "",
|
||||||
@@ -1619,11 +1625,13 @@
|
|||||||
"changestatus": "Cambiar Estado",
|
"changestatus": "Cambiar Estado",
|
||||||
"changestimator": "",
|
"changestimator": "",
|
||||||
"convert": "Convertir",
|
"convert": "Convertir",
|
||||||
|
"convertwithoutearlyro": "",
|
||||||
"createiou": "",
|
"createiou": "",
|
||||||
"deliver": "",
|
"deliver": "",
|
||||||
"deliver_quick": "",
|
"deliver_quick": "",
|
||||||
"dms": {
|
"dms": {
|
||||||
"addpayer": "",
|
"addpayer": "",
|
||||||
|
"createearlyro": "",
|
||||||
"createnewcustomer": "",
|
"createnewcustomer": "",
|
||||||
"findmakemodelcode": "",
|
"findmakemodelcode": "",
|
||||||
"getmakes": "",
|
"getmakes": "",
|
||||||
@@ -1632,6 +1640,7 @@
|
|||||||
},
|
},
|
||||||
"post": "",
|
"post": "",
|
||||||
"refetchmakesmodels": "",
|
"refetchmakesmodels": "",
|
||||||
|
"update_ro": "",
|
||||||
"usegeneric": "",
|
"usegeneric": "",
|
||||||
"useselected": ""
|
"useselected": ""
|
||||||
},
|
},
|
||||||
@@ -1700,8 +1709,8 @@
|
|||||||
"actual_in": "Real en",
|
"actual_in": "Real en",
|
||||||
"acv_amount": "",
|
"acv_amount": "",
|
||||||
"adjustment_bottom_line": "Ajustes",
|
"adjustment_bottom_line": "Ajustes",
|
||||||
"admin_clerk": "",
|
|
||||||
"adjustmenthours": "",
|
"adjustmenthours": "",
|
||||||
|
"admin_clerk": "",
|
||||||
"alt_transport": "",
|
"alt_transport": "",
|
||||||
"area_of_damage_impact": {
|
"area_of_damage_impact": {
|
||||||
"10": "",
|
"10": "",
|
||||||
@@ -1782,9 +1791,8 @@
|
|||||||
"ded_status": "Estado deducible",
|
"ded_status": "Estado deducible",
|
||||||
"depreciation_taxes": "Depreciación / Impuestos",
|
"depreciation_taxes": "Depreciación / Impuestos",
|
||||||
"dms": {
|
"dms": {
|
||||||
"first_name": "",
|
|
||||||
"last_name": "",
|
|
||||||
"address": "",
|
"address": "",
|
||||||
|
"advisor": "",
|
||||||
"amount": "",
|
"amount": "",
|
||||||
"center": "",
|
"center": "",
|
||||||
"control_type": {
|
"control_type": {
|
||||||
@@ -1792,29 +1800,36 @@
|
|||||||
},
|
},
|
||||||
"cost": "",
|
"cost": "",
|
||||||
"cost_dms_acctnumber": "",
|
"cost_dms_acctnumber": "",
|
||||||
|
"customer": "",
|
||||||
"dms_make": "",
|
"dms_make": "",
|
||||||
"dms_model": "",
|
"dms_model": "",
|
||||||
"dms_model_override": "",
|
"dms_model_override": "",
|
||||||
"make_override": "",
|
|
||||||
"advisor": "",
|
|
||||||
"dms_unsold": "",
|
"dms_unsold": "",
|
||||||
"dms_wip_acctnumber": "",
|
"dms_wip_acctnumber": "",
|
||||||
|
"first_name": "",
|
||||||
"id": "",
|
"id": "",
|
||||||
"inservicedate": "",
|
"inservicedate": "",
|
||||||
"journal": "",
|
"journal": "",
|
||||||
|
"last_name": "",
|
||||||
"lines": "",
|
"lines": "",
|
||||||
|
"make_override": "",
|
||||||
"name1": "",
|
"name1": "",
|
||||||
"payer": {
|
"payer": {
|
||||||
"amount": "",
|
"amount": "",
|
||||||
"control_type": "",
|
"control_type": "",
|
||||||
"controlnumber": "",
|
"controlnumber": "",
|
||||||
"dms_acctnumber": "",
|
"dms_acctnumber": "",
|
||||||
"name": ""
|
"name": "",
|
||||||
|
"payer_type": ""
|
||||||
},
|
},
|
||||||
"sale": "",
|
"sale": "",
|
||||||
"sale_dms_acctnumber": "",
|
"sale_dms_acctnumber": "",
|
||||||
"story": "",
|
"story": "",
|
||||||
"vinowner": ""
|
"vinowner": "",
|
||||||
|
"rr_opcode": "",
|
||||||
|
"rr_opcode_prefix": "",
|
||||||
|
"rr_opcode_suffix": "",
|
||||||
|
"rr_opcode_base": ""
|
||||||
},
|
},
|
||||||
"dms_allocation": "",
|
"dms_allocation": "",
|
||||||
"driveable": "",
|
"driveable": "",
|
||||||
@@ -2099,6 +2114,11 @@
|
|||||||
"damageto": "",
|
"damageto": "",
|
||||||
"defaultstory": "",
|
"defaultstory": "",
|
||||||
"disablebillwip": "",
|
"disablebillwip": "",
|
||||||
|
"earlyro": {
|
||||||
|
"created": "",
|
||||||
|
"fields": "",
|
||||||
|
"willupdate": ""
|
||||||
|
},
|
||||||
"invoicedatefuture": "",
|
"invoicedatefuture": "",
|
||||||
"kmoutnotgreaterthankmin": "",
|
"kmoutnotgreaterthankmin": "",
|
||||||
"logs": "",
|
"logs": "",
|
||||||
@@ -2256,6 +2276,7 @@
|
|||||||
"delete": "",
|
"delete": "",
|
||||||
"deleted": "Trabajo eliminado con éxito.",
|
"deleted": "Trabajo eliminado con éxito.",
|
||||||
"duplicated": "",
|
"duplicated": "",
|
||||||
|
"early_ro_created": "",
|
||||||
"exported": "",
|
"exported": "",
|
||||||
"invoiced": "",
|
"invoiced": "",
|
||||||
"ioucreated": "",
|
"ioucreated": "",
|
||||||
@@ -2444,6 +2465,7 @@
|
|||||||
"labels": {
|
"labels": {
|
||||||
"addlabel": "",
|
"addlabel": "",
|
||||||
"archive": "",
|
"archive": "",
|
||||||
|
"mark_unread": "",
|
||||||
"maxtenimages": "",
|
"maxtenimages": "",
|
||||||
"messaging": "Mensajería",
|
"messaging": "Mensajería",
|
||||||
"no_consent": "",
|
"no_consent": "",
|
||||||
@@ -2456,8 +2478,7 @@
|
|||||||
"selectmedia": "",
|
"selectmedia": "",
|
||||||
"sentby": "",
|
"sentby": "",
|
||||||
"typeamessage": "Enviar un mensaje...",
|
"typeamessage": "Enviar un mensaje...",
|
||||||
"unarchive": "",
|
"unarchive": ""
|
||||||
"mark_unread": ""
|
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"conversation_list": ""
|
"conversation_list": ""
|
||||||
@@ -2611,20 +2632,20 @@
|
|||||||
"name": ""
|
"name": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"cell": "",
|
||||||
"create_new": "Crea un nuevo registro de propietario.",
|
"create_new": "Crea un nuevo registro de propietario.",
|
||||||
"deleteconfirm": "",
|
"deleteconfirm": "",
|
||||||
|
"email": "",
|
||||||
"existing_owners": "Propietarios existentes",
|
"existing_owners": "Propietarios existentes",
|
||||||
"fromclaim": "",
|
"fromclaim": "",
|
||||||
"fromowner": "",
|
"fromowner": "",
|
||||||
"relatedjobs": "",
|
|
||||||
"updateowner": "",
|
|
||||||
"work": "",
|
|
||||||
"home": "",
|
"home": "",
|
||||||
"cell": "",
|
|
||||||
"other": "",
|
"other": "",
|
||||||
"email": "",
|
|
||||||
"phone": "",
|
"phone": "",
|
||||||
"sms": ""
|
"relatedjobs": "",
|
||||||
|
"sms": "",
|
||||||
|
"updateowner": "",
|
||||||
|
"work": ""
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"delete": "",
|
"delete": "",
|
||||||
@@ -2635,6 +2656,10 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"order": "Pedido de piezas",
|
"order": "Pedido de piezas",
|
||||||
"orderinhouse": ""
|
"orderinhouse": ""
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"view_counts_only": "",
|
||||||
|
"view_timestamps": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parts_dispatch": {
|
"parts_dispatch": {
|
||||||
@@ -2984,8 +3009,6 @@
|
|||||||
"settings": ""
|
"settings": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"click_for_statuses": "",
|
|
||||||
"partsreceived": "",
|
|
||||||
"actual_in": "",
|
"actual_in": "",
|
||||||
"addnewprofile": "",
|
"addnewprofile": "",
|
||||||
"alert": "",
|
"alert": "",
|
||||||
@@ -3004,6 +3027,7 @@
|
|||||||
"card_size": "",
|
"card_size": "",
|
||||||
"cardcolor": "",
|
"cardcolor": "",
|
||||||
"cardsettings": "",
|
"cardsettings": "",
|
||||||
|
"click_for_statuses": "",
|
||||||
"clm_no": "",
|
"clm_no": "",
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"compact": "",
|
"compact": "",
|
||||||
@@ -3024,6 +3048,7 @@
|
|||||||
"orientation": "",
|
"orientation": "",
|
||||||
"ownr_nm": "",
|
"ownr_nm": "",
|
||||||
"paintpriority": "",
|
"paintpriority": "",
|
||||||
|
"partsreceived": "",
|
||||||
"partsstatus": "",
|
"partsstatus": "",
|
||||||
"production_note": "",
|
"production_note": "",
|
||||||
"refinishhours": "",
|
"refinishhours": "",
|
||||||
@@ -3570,18 +3595,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
"simplified-parts-jobs": "",
|
|
||||||
"parts": "",
|
|
||||||
"parts_settings": "",
|
|
||||||
"accounting-payables": "",
|
"accounting-payables": "",
|
||||||
"accounting-payments": "",
|
"accounting-payments": "",
|
||||||
"accounting-receivables": "",
|
"accounting-receivables": "",
|
||||||
"all_tasks": "",
|
"all_tasks": "",
|
||||||
"app": "",
|
"app": "",
|
||||||
"bc": {
|
"bc": {
|
||||||
"simplified-parts-jobs": "",
|
|
||||||
"parts": "",
|
|
||||||
"parts_settings": "",
|
|
||||||
"accounting-payables": "",
|
"accounting-payables": "",
|
||||||
"accounting-payments": "",
|
"accounting-payments": "",
|
||||||
"accounting-receivables": "",
|
"accounting-receivables": "",
|
||||||
@@ -3613,7 +3632,9 @@
|
|||||||
"my_tasks": "",
|
"my_tasks": "",
|
||||||
"owner-detail": "",
|
"owner-detail": "",
|
||||||
"owners": "",
|
"owners": "",
|
||||||
|
"parts": "",
|
||||||
"parts-queue": "",
|
"parts-queue": "",
|
||||||
|
"parts_settings": "",
|
||||||
"payments-all": "",
|
"payments-all": "",
|
||||||
"phonebook": "",
|
"phonebook": "",
|
||||||
"productionboard": "",
|
"productionboard": "",
|
||||||
@@ -3625,6 +3646,7 @@
|
|||||||
"shop-csi": "",
|
"shop-csi": "",
|
||||||
"shop-templates": "",
|
"shop-templates": "",
|
||||||
"shop-vendors": "",
|
"shop-vendors": "",
|
||||||
|
"simplified-parts-jobs": "",
|
||||||
"tasks": "",
|
"tasks": "",
|
||||||
"temporarydocs": "",
|
"temporarydocs": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
@@ -3660,7 +3682,9 @@
|
|||||||
"my_tasks": "",
|
"my_tasks": "",
|
||||||
"owners": "Todos los propietarios | {{app}}",
|
"owners": "Todos los propietarios | {{app}}",
|
||||||
"owners-detail": "",
|
"owners-detail": "",
|
||||||
|
"parts": "",
|
||||||
"parts-queue": "",
|
"parts-queue": "",
|
||||||
|
"parts_settings": "",
|
||||||
"payments-all": "",
|
"payments-all": "",
|
||||||
"phonebook": "",
|
"phonebook": "",
|
||||||
"productionboard": "",
|
"productionboard": "",
|
||||||
@@ -3676,6 +3700,7 @@
|
|||||||
"shop-csi": "",
|
"shop-csi": "",
|
||||||
"shop-templates": "",
|
"shop-templates": "",
|
||||||
"shop_vendors": "Vendedores | {{app}}",
|
"shop_vendors": "Vendedores | {{app}}",
|
||||||
|
"simplified-parts-jobs": "",
|
||||||
"tasks": "",
|
"tasks": "",
|
||||||
"techconsole": "{{app}}",
|
"techconsole": "{{app}}",
|
||||||
"techjobclock": "{{app}}",
|
"techjobclock": "{{app}}",
|
||||||
@@ -3836,10 +3861,10 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"changepassword": "",
|
"changepassword": "",
|
||||||
"signout": "desconectar",
|
"dark_theme": "",
|
||||||
"updateprofile": "Actualización del perfil",
|
|
||||||
"light_theme": "",
|
"light_theme": "",
|
||||||
"dark_theme": ""
|
"signout": "desconectar",
|
||||||
|
"updateprofile": "Actualización del perfil"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"updating": ""
|
"updating": ""
|
||||||
@@ -3853,14 +3878,14 @@
|
|||||||
"labels": {
|
"labels": {
|
||||||
"actions": "",
|
"actions": "",
|
||||||
"changepassword": "",
|
"changepassword": "",
|
||||||
"profileinfo": "",
|
|
||||||
"user_settings": "",
|
|
||||||
"play_sound_for_new_messages": "",
|
|
||||||
"notification_sound_on": "",
|
|
||||||
"notification_sound_off": "",
|
|
||||||
"notification_sound_enabled": "",
|
|
||||||
"notification_sound_disabled": "",
|
"notification_sound_disabled": "",
|
||||||
"notification_sound_help": ""
|
"notification_sound_enabled": "",
|
||||||
|
"notification_sound_help": "",
|
||||||
|
"notification_sound_off": "",
|
||||||
|
"notification_sound_on": "",
|
||||||
|
"play_sound_for_new_messages": "",
|
||||||
|
"profileinfo": "",
|
||||||
|
"user_settings": ""
|
||||||
},
|
},
|
||||||
"successess": {
|
"successess": {
|
||||||
"passwordchanged": ""
|
"passwordchanged": ""
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"arrivedon": "Arrivé le:",
|
"arrivedon": "Arrivé le:",
|
||||||
"arrivingjobs": "",
|
"arrivingjobs": "",
|
||||||
"blocked": "",
|
"blocked": "",
|
||||||
|
"bp": "",
|
||||||
"cancelledappointment": "Rendez-vous annulé pour:",
|
"cancelledappointment": "Rendez-vous annulé pour:",
|
||||||
"completingjobs": "",
|
"completingjobs": "",
|
||||||
"dataconsistency": "",
|
"dataconsistency": "",
|
||||||
@@ -59,18 +60,17 @@
|
|||||||
"noarrivingjobs": "",
|
"noarrivingjobs": "",
|
||||||
"nocompletingjobs": "",
|
"nocompletingjobs": "",
|
||||||
"nodateselected": "Aucune date n'a été sélectionnée.",
|
"nodateselected": "Aucune date n'a été sélectionnée.",
|
||||||
|
"owner": "",
|
||||||
"priorappointments": "Rendez-vous précédents",
|
"priorappointments": "Rendez-vous précédents",
|
||||||
"reminder": "",
|
"reminder": "",
|
||||||
|
"ro_number": "",
|
||||||
|
"scheduled_completion": "",
|
||||||
"scheduledfor": "Rendez-vous prévu pour:",
|
"scheduledfor": "Rendez-vous prévu pour:",
|
||||||
"severalerrorsfound": "",
|
"severalerrorsfound": "",
|
||||||
"smartscheduling": "",
|
"smartscheduling": "",
|
||||||
"smspaymentreminder": "",
|
"smspaymentreminder": "",
|
||||||
"suggesteddates": "",
|
"suggesteddates": "",
|
||||||
"ro_number": "",
|
"vehicle": ""
|
||||||
"owner": "",
|
|
||||||
"vehicle": "",
|
|
||||||
"bp": "",
|
|
||||||
"scheduled_completion": ""
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"canceled": "Rendez-vous annulé avec succès.",
|
"canceled": "Rendez-vous annulé avec succès.",
|
||||||
@@ -90,6 +90,11 @@
|
|||||||
"actions": "actes"
|
"actions": "actes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"audio": {
|
||||||
|
"manager": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"audit": {
|
"audit": {
|
||||||
"fields": {
|
"fields": {
|
||||||
"cc": "",
|
"cc": "",
|
||||||
@@ -149,11 +154,6 @@
|
|||||||
"tasks_updated": ""
|
"tasks_updated": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"audio": {
|
|
||||||
"manager": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"billlines": {
|
"billlines": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"newline": ""
|
"newline": ""
|
||||||
@@ -281,9 +281,9 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"creatingdefaultview": "",
|
"creatingdefaultview": "",
|
||||||
|
"duplicate_insurance_company": "",
|
||||||
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.",
|
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.",
|
||||||
"saving": "",
|
"saving": ""
|
||||||
"duplicate_insurance_company": ""
|
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"ReceivableCustomField": "",
|
"ReceivableCustomField": "",
|
||||||
@@ -564,21 +564,18 @@
|
|||||||
"responsibilitycenter_tax_tier": "",
|
"responsibilitycenter_tax_tier": "",
|
||||||
"responsibilitycenter_tax_type": "",
|
"responsibilitycenter_tax_type": "",
|
||||||
"responsibilitycenters": {
|
"responsibilitycenters": {
|
||||||
"gogcode": "",
|
|
||||||
"item_type": "Item Type",
|
|
||||||
"item_type_gog": "",
|
|
||||||
"item_type_paint": "",
|
|
||||||
"item_type_freight": "",
|
|
||||||
"taxable_flag": "",
|
|
||||||
"taxable": "",
|
|
||||||
"nontaxable": "",
|
|
||||||
"ap": "",
|
"ap": "",
|
||||||
"ar": "",
|
"ar": "",
|
||||||
"ats": "",
|
"ats": "",
|
||||||
"federal_tax": "",
|
"federal_tax": "",
|
||||||
"federal_tax_itc": "",
|
"federal_tax_itc": "",
|
||||||
|
"gogcode": "",
|
||||||
"gst_override": "",
|
"gst_override": "",
|
||||||
"invoiceexemptcode": "",
|
"invoiceexemptcode": "",
|
||||||
|
"item_type": "Item Type",
|
||||||
|
"item_type_freight": "",
|
||||||
|
"item_type_gog": "",
|
||||||
|
"item_type_paint": "",
|
||||||
"itemexemptcode": "",
|
"itemexemptcode": "",
|
||||||
"la1": "",
|
"la1": "",
|
||||||
"la2": "",
|
"la2": "",
|
||||||
@@ -597,6 +594,7 @@
|
|||||||
"local_tax": "",
|
"local_tax": "",
|
||||||
"mapa": "",
|
"mapa": "",
|
||||||
"mash": "",
|
"mash": "",
|
||||||
|
"nontaxable": "",
|
||||||
"paa": "",
|
"paa": "",
|
||||||
"pac": "",
|
"pac": "",
|
||||||
"pag": "",
|
"pag": "",
|
||||||
@@ -617,6 +615,8 @@
|
|||||||
"state": ""
|
"state": ""
|
||||||
},
|
},
|
||||||
"state_tax": "",
|
"state_tax": "",
|
||||||
|
"taxable": "",
|
||||||
|
"taxable_flag": "",
|
||||||
"tow": ""
|
"tow": ""
|
||||||
},
|
},
|
||||||
"schedule_end_time": "",
|
"schedule_end_time": "",
|
||||||
@@ -678,8 +678,6 @@
|
|||||||
"zip_post": ""
|
"zip_post": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"parts_shop_management": "",
|
|
||||||
"parts_vendor_management": "",
|
|
||||||
"2tiername": "",
|
"2tiername": "",
|
||||||
"2tiersetup": "",
|
"2tiersetup": "",
|
||||||
"2tiersource": "",
|
"2tiersource": "",
|
||||||
@@ -702,11 +700,11 @@
|
|||||||
"payers": ""
|
"payers": ""
|
||||||
},
|
},
|
||||||
"cdk_dealerid": "",
|
"cdk_dealerid": "",
|
||||||
"rr_dealerid": "",
|
|
||||||
"costsmapping": "",
|
"costsmapping": "",
|
||||||
"dms_allocations": "",
|
"dms_allocations": "",
|
||||||
"pbs_serialnumber": "",
|
"pbs_serialnumber": "",
|
||||||
"profitsmapping": "",
|
"profitsmapping": "",
|
||||||
|
"rr_dealerid": "",
|
||||||
"title": ""
|
"title": ""
|
||||||
},
|
},
|
||||||
"emaillater": "",
|
"emaillater": "",
|
||||||
@@ -733,6 +731,8 @@
|
|||||||
"followers": ""
|
"followers": ""
|
||||||
},
|
},
|
||||||
"orderstatuses": "",
|
"orderstatuses": "",
|
||||||
|
"parts_shop_management": "",
|
||||||
|
"parts_vendor_management": "",
|
||||||
"partslocations": "",
|
"partslocations": "",
|
||||||
"partsscan": "",
|
"partsscan": "",
|
||||||
"printlater": "",
|
"printlater": "",
|
||||||
@@ -1047,7 +1047,9 @@
|
|||||||
},
|
},
|
||||||
"dms": {
|
"dms": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"alreadyexported": ""
|
"alreadyexported": "",
|
||||||
|
"earlyrorequired": "",
|
||||||
|
"earlyrorequired.message": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"refreshallocations": ""
|
"refreshallocations": ""
|
||||||
@@ -1244,9 +1246,11 @@
|
|||||||
"deselectall": "",
|
"deselectall": "",
|
||||||
"download": "",
|
"download": "",
|
||||||
"edit": "modifier",
|
"edit": "modifier",
|
||||||
|
"gotoadmin": "",
|
||||||
"login": "",
|
"login": "",
|
||||||
"next": "",
|
"next": "",
|
||||||
"ok": "",
|
"ok": "",
|
||||||
|
"optional": "",
|
||||||
"previous": "",
|
"previous": "",
|
||||||
"print": "",
|
"print": "",
|
||||||
"refresh": "",
|
"refresh": "",
|
||||||
@@ -1257,6 +1261,7 @@
|
|||||||
"save": "sauvegarder",
|
"save": "sauvegarder",
|
||||||
"saveandnew": "",
|
"saveandnew": "",
|
||||||
"saveas": "",
|
"saveas": "",
|
||||||
|
"select": "",
|
||||||
"selectall": "",
|
"selectall": "",
|
||||||
"send": "",
|
"send": "",
|
||||||
"sendbysms": "",
|
"sendbysms": "",
|
||||||
@@ -1286,8 +1291,7 @@
|
|||||||
"vehicle": ""
|
"vehicle": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"selected": "",
|
"apply": "",
|
||||||
"settings": "",
|
|
||||||
"actions": "actes",
|
"actions": "actes",
|
||||||
"areyousure": "",
|
"areyousure": "",
|
||||||
"barcode": "code à barre",
|
"barcode": "code à barre",
|
||||||
@@ -1341,8 +1345,10 @@
|
|||||||
"search": "Chercher...",
|
"search": "Chercher...",
|
||||||
"searchresults": "",
|
"searchresults": "",
|
||||||
"selectdate": "",
|
"selectdate": "",
|
||||||
|
"selected": "",
|
||||||
"sendagain": "",
|
"sendagain": "",
|
||||||
"sendby": "",
|
"sendby": "",
|
||||||
|
"settings": "",
|
||||||
"signin": "",
|
"signin": "",
|
||||||
"sms": "",
|
"sms": "",
|
||||||
"status": "",
|
"status": "",
|
||||||
@@ -1585,13 +1591,13 @@
|
|||||||
"labels": {
|
"labels": {
|
||||||
"adjustmenttobeadded": "",
|
"adjustmenttobeadded": "",
|
||||||
"billref": "",
|
"billref": "",
|
||||||
|
"bulk_location_help": "",
|
||||||
"convertedtolabor": "",
|
"convertedtolabor": "",
|
||||||
"edit": "Ligne d'édition",
|
"edit": "Ligne d'édition",
|
||||||
"ioucreated": "",
|
"ioucreated": "",
|
||||||
"new": "Nouvelle ligne",
|
"new": "Nouvelle ligne",
|
||||||
"nostatus": "",
|
"nostatus": "",
|
||||||
"presets": "",
|
"presets": ""
|
||||||
"bulk_location_help": ""
|
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"created": "",
|
"created": "",
|
||||||
@@ -1619,11 +1625,13 @@
|
|||||||
"changestatus": "Changer le statut",
|
"changestatus": "Changer le statut",
|
||||||
"changestimator": "",
|
"changestimator": "",
|
||||||
"convert": "Convertir",
|
"convert": "Convertir",
|
||||||
|
"convertwithoutearlyro": "",
|
||||||
"createiou": "",
|
"createiou": "",
|
||||||
"deliver": "",
|
"deliver": "",
|
||||||
"deliver_quick": "",
|
"deliver_quick": "",
|
||||||
"dms": {
|
"dms": {
|
||||||
"addpayer": "",
|
"addpayer": "",
|
||||||
|
"createearlyro": "",
|
||||||
"createnewcustomer": "",
|
"createnewcustomer": "",
|
||||||
"findmakemodelcode": "",
|
"findmakemodelcode": "",
|
||||||
"getmakes": "",
|
"getmakes": "",
|
||||||
@@ -1632,6 +1640,7 @@
|
|||||||
},
|
},
|
||||||
"post": "",
|
"post": "",
|
||||||
"refetchmakesmodels": "",
|
"refetchmakesmodels": "",
|
||||||
|
"update_ro": "",
|
||||||
"usegeneric": "",
|
"usegeneric": "",
|
||||||
"useselected": ""
|
"useselected": ""
|
||||||
},
|
},
|
||||||
@@ -1699,9 +1708,9 @@
|
|||||||
"actual_delivery": "Livraison réelle",
|
"actual_delivery": "Livraison réelle",
|
||||||
"actual_in": "En réel",
|
"actual_in": "En réel",
|
||||||
"acv_amount": "",
|
"acv_amount": "",
|
||||||
"admin_clerk": "",
|
|
||||||
"adjustment_bottom_line": "Ajustements",
|
"adjustment_bottom_line": "Ajustements",
|
||||||
"adjustmenthours": "",
|
"adjustmenthours": "",
|
||||||
|
"admin_clerk": "",
|
||||||
"alt_transport": "",
|
"alt_transport": "",
|
||||||
"area_of_damage_impact": {
|
"area_of_damage_impact": {
|
||||||
"10": "",
|
"10": "",
|
||||||
@@ -1782,9 +1791,8 @@
|
|||||||
"ded_status": "Statut de franchise",
|
"ded_status": "Statut de franchise",
|
||||||
"depreciation_taxes": "Amortissement / taxes",
|
"depreciation_taxes": "Amortissement / taxes",
|
||||||
"dms": {
|
"dms": {
|
||||||
"first_name": "",
|
|
||||||
"last_name": "",
|
|
||||||
"address": "",
|
"address": "",
|
||||||
|
"advisor": "",
|
||||||
"amount": "",
|
"amount": "",
|
||||||
"center": "",
|
"center": "",
|
||||||
"control_type": {
|
"control_type": {
|
||||||
@@ -1792,29 +1800,36 @@
|
|||||||
},
|
},
|
||||||
"cost": "",
|
"cost": "",
|
||||||
"cost_dms_acctnumber": "",
|
"cost_dms_acctnumber": "",
|
||||||
|
"customer": "",
|
||||||
"dms_make": "",
|
"dms_make": "",
|
||||||
"dms_model": "",
|
"dms_model": "",
|
||||||
"dms_model_override": "",
|
"dms_model_override": "",
|
||||||
"make_override": "",
|
|
||||||
"advisor": "",
|
|
||||||
"dms_unsold": "",
|
"dms_unsold": "",
|
||||||
"dms_wip_acctnumber": "",
|
"dms_wip_acctnumber": "",
|
||||||
|
"first_name": "",
|
||||||
"id": "",
|
"id": "",
|
||||||
"inservicedate": "",
|
"inservicedate": "",
|
||||||
"journal": "",
|
"journal": "",
|
||||||
|
"last_name": "",
|
||||||
"lines": "",
|
"lines": "",
|
||||||
|
"make_override": "",
|
||||||
"name1": "",
|
"name1": "",
|
||||||
"payer": {
|
"payer": {
|
||||||
"amount": "",
|
"amount": "",
|
||||||
"control_type": "",
|
"control_type": "",
|
||||||
"controlnumber": "",
|
"controlnumber": "",
|
||||||
"dms_acctnumber": "",
|
"dms_acctnumber": "",
|
||||||
"name": ""
|
"name": "",
|
||||||
|
"payer_type": ""
|
||||||
},
|
},
|
||||||
"sale": "",
|
"sale": "",
|
||||||
"sale_dms_acctnumber": "",
|
"sale_dms_acctnumber": "",
|
||||||
"story": "",
|
"story": "",
|
||||||
"vinowner": ""
|
"vinowner": "",
|
||||||
|
"rr_opcode": "",
|
||||||
|
"rr_opcode_prefix": "",
|
||||||
|
"rr_opcode_suffix": "",
|
||||||
|
"rr_opcode_base": ""
|
||||||
},
|
},
|
||||||
"dms_allocation": "",
|
"dms_allocation": "",
|
||||||
"driveable": "",
|
"driveable": "",
|
||||||
@@ -2099,6 +2114,11 @@
|
|||||||
"damageto": "",
|
"damageto": "",
|
||||||
"defaultstory": "",
|
"defaultstory": "",
|
||||||
"disablebillwip": "",
|
"disablebillwip": "",
|
||||||
|
"earlyro": {
|
||||||
|
"created": "",
|
||||||
|
"fields": "",
|
||||||
|
"willupdate": ""
|
||||||
|
},
|
||||||
"invoicedatefuture": "",
|
"invoicedatefuture": "",
|
||||||
"kmoutnotgreaterthankmin": "",
|
"kmoutnotgreaterthankmin": "",
|
||||||
"logs": "",
|
"logs": "",
|
||||||
@@ -2256,6 +2276,7 @@
|
|||||||
"delete": "",
|
"delete": "",
|
||||||
"deleted": "Le travail a bien été supprimé.",
|
"deleted": "Le travail a bien été supprimé.",
|
||||||
"duplicated": "",
|
"duplicated": "",
|
||||||
|
"early_ro_created": "",
|
||||||
"exported": "",
|
"exported": "",
|
||||||
"invoiced": "",
|
"invoiced": "",
|
||||||
"ioucreated": "",
|
"ioucreated": "",
|
||||||
@@ -2433,7 +2454,6 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"link": "",
|
"link": "",
|
||||||
"new": "",
|
"new": "",
|
||||||
|
|
||||||
"openchat": ""
|
"openchat": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
@@ -2445,6 +2465,7 @@
|
|||||||
"labels": {
|
"labels": {
|
||||||
"addlabel": "",
|
"addlabel": "",
|
||||||
"archive": "",
|
"archive": "",
|
||||||
|
"mark_unread": "",
|
||||||
"maxtenimages": "",
|
"maxtenimages": "",
|
||||||
"messaging": "Messagerie",
|
"messaging": "Messagerie",
|
||||||
"no_consent": "",
|
"no_consent": "",
|
||||||
@@ -2457,8 +2478,7 @@
|
|||||||
"selectmedia": "",
|
"selectmedia": "",
|
||||||
"sentby": "",
|
"sentby": "",
|
||||||
"typeamessage": "Envoyer un message...",
|
"typeamessage": "Envoyer un message...",
|
||||||
"unarchive": "",
|
"unarchive": ""
|
||||||
"mark_unread": ""
|
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"conversation_list": ""
|
"conversation_list": ""
|
||||||
@@ -2612,20 +2632,20 @@
|
|||||||
"name": ""
|
"name": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"cell": "",
|
||||||
"create_new": "Créez un nouvel enregistrement de propriétaire.",
|
"create_new": "Créez un nouvel enregistrement de propriétaire.",
|
||||||
"deleteconfirm": "",
|
"deleteconfirm": "",
|
||||||
|
"email": "",
|
||||||
"existing_owners": "Propriétaires existants",
|
"existing_owners": "Propriétaires existants",
|
||||||
"fromclaim": "",
|
"fromclaim": "",
|
||||||
"fromowner": "",
|
"fromowner": "",
|
||||||
"relatedjobs": "",
|
|
||||||
"updateowner": "",
|
|
||||||
"work": "",
|
|
||||||
"home": "",
|
"home": "",
|
||||||
"cell": "",
|
|
||||||
"other": "",
|
"other": "",
|
||||||
"email": "",
|
|
||||||
"phone": "",
|
"phone": "",
|
||||||
"sms": ""
|
"relatedjobs": "",
|
||||||
|
"sms": "",
|
||||||
|
"updateowner": "",
|
||||||
|
"work": ""
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"delete": "",
|
"delete": "",
|
||||||
@@ -2636,6 +2656,10 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"order": "Commander des pièces",
|
"order": "Commander des pièces",
|
||||||
"orderinhouse": ""
|
"orderinhouse": ""
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"view_counts_only": "",
|
||||||
|
"view_timestamps": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parts_dispatch": {
|
"parts_dispatch": {
|
||||||
@@ -2985,8 +3009,6 @@
|
|||||||
"settings": ""
|
"settings": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"click_for_statuses": "",
|
|
||||||
"partsreceived": "",
|
|
||||||
"actual_in": "",
|
"actual_in": "",
|
||||||
"addnewprofile": "",
|
"addnewprofile": "",
|
||||||
"alert": "",
|
"alert": "",
|
||||||
@@ -3005,6 +3027,7 @@
|
|||||||
"card_size": "",
|
"card_size": "",
|
||||||
"cardcolor": "",
|
"cardcolor": "",
|
||||||
"cardsettings": "",
|
"cardsettings": "",
|
||||||
|
"click_for_statuses": "",
|
||||||
"clm_no": "",
|
"clm_no": "",
|
||||||
"comment": "",
|
"comment": "",
|
||||||
"compact": "",
|
"compact": "",
|
||||||
@@ -3025,6 +3048,7 @@
|
|||||||
"orientation": "",
|
"orientation": "",
|
||||||
"ownr_nm": "",
|
"ownr_nm": "",
|
||||||
"paintpriority": "",
|
"paintpriority": "",
|
||||||
|
"partsreceived": "",
|
||||||
"partsstatus": "",
|
"partsstatus": "",
|
||||||
"production_note": "",
|
"production_note": "",
|
||||||
"refinishhours": "",
|
"refinishhours": "",
|
||||||
@@ -3571,18 +3595,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
"simplified-parts-jobs": "",
|
|
||||||
"parts": "",
|
|
||||||
"parts_settings": "",
|
|
||||||
"accounting-payables": "",
|
"accounting-payables": "",
|
||||||
"accounting-payments": "",
|
"accounting-payments": "",
|
||||||
"accounting-receivables": "",
|
"accounting-receivables": "",
|
||||||
"all_tasks": "",
|
"all_tasks": "",
|
||||||
"app": "",
|
"app": "",
|
||||||
"bc": {
|
"bc": {
|
||||||
"simplified-parts-jobs": "",
|
|
||||||
"parts": "",
|
|
||||||
"parts_settings": "",
|
|
||||||
"accounting-payables": "",
|
"accounting-payables": "",
|
||||||
"accounting-payments": "",
|
"accounting-payments": "",
|
||||||
"accounting-receivables": "",
|
"accounting-receivables": "",
|
||||||
@@ -3614,7 +3632,9 @@
|
|||||||
"my_tasks": "",
|
"my_tasks": "",
|
||||||
"owner-detail": "",
|
"owner-detail": "",
|
||||||
"owners": "",
|
"owners": "",
|
||||||
|
"parts": "",
|
||||||
"parts-queue": "",
|
"parts-queue": "",
|
||||||
|
"parts_settings": "",
|
||||||
"payments-all": "",
|
"payments-all": "",
|
||||||
"phonebook": "",
|
"phonebook": "",
|
||||||
"productionboard": "",
|
"productionboard": "",
|
||||||
@@ -3626,6 +3646,7 @@
|
|||||||
"shop-csi": "",
|
"shop-csi": "",
|
||||||
"shop-templates": "",
|
"shop-templates": "",
|
||||||
"shop-vendors": "",
|
"shop-vendors": "",
|
||||||
|
"simplified-parts-jobs": "",
|
||||||
"tasks": "",
|
"tasks": "",
|
||||||
"temporarydocs": "",
|
"temporarydocs": "",
|
||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
@@ -3661,7 +3682,9 @@
|
|||||||
"my_tasks": "",
|
"my_tasks": "",
|
||||||
"owners": "Tous les propriétaires | {{app}}",
|
"owners": "Tous les propriétaires | {{app}}",
|
||||||
"owners-detail": "",
|
"owners-detail": "",
|
||||||
|
"parts": "",
|
||||||
"parts-queue": "",
|
"parts-queue": "",
|
||||||
|
"parts_settings": "",
|
||||||
"payments-all": "",
|
"payments-all": "",
|
||||||
"phonebook": "",
|
"phonebook": "",
|
||||||
"productionboard": "",
|
"productionboard": "",
|
||||||
@@ -3677,6 +3700,7 @@
|
|||||||
"shop-csi": "",
|
"shop-csi": "",
|
||||||
"shop-templates": "",
|
"shop-templates": "",
|
||||||
"shop_vendors": "Vendeurs | {{app}}",
|
"shop_vendors": "Vendeurs | {{app}}",
|
||||||
|
"simplified-parts-jobs": "",
|
||||||
"tasks": "",
|
"tasks": "",
|
||||||
"techconsole": "{{app}}",
|
"techconsole": "{{app}}",
|
||||||
"techjobclock": "{{app}}",
|
"techjobclock": "{{app}}",
|
||||||
@@ -3837,10 +3861,10 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"changepassword": "",
|
"changepassword": "",
|
||||||
"signout": "Déconnexion",
|
"dark_theme": "",
|
||||||
"updateprofile": "Mettre à jour le profil",
|
|
||||||
"light_theme": "",
|
"light_theme": "",
|
||||||
"dark_theme": ""
|
"signout": "Déconnexion",
|
||||||
|
"updateprofile": "Mettre à jour le profil"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"updating": ""
|
"updating": ""
|
||||||
@@ -3854,14 +3878,14 @@
|
|||||||
"labels": {
|
"labels": {
|
||||||
"actions": "",
|
"actions": "",
|
||||||
"changepassword": "",
|
"changepassword": "",
|
||||||
"profileinfo": "",
|
|
||||||
"user_settings": "",
|
|
||||||
"play_sound_for_new_messages": "",
|
|
||||||
"notification_sound_on": "",
|
|
||||||
"notification_sound_off": "",
|
|
||||||
"notification_sound_enabled": "",
|
|
||||||
"notification_sound_disabled": "",
|
"notification_sound_disabled": "",
|
||||||
"notification_sound_help": ""
|
"notification_sound_enabled": "",
|
||||||
|
"notification_sound_help": "",
|
||||||
|
"notification_sound_off": "",
|
||||||
|
"notification_sound_on": "",
|
||||||
|
"play_sound_for_new_messages": "",
|
||||||
|
"profileinfo": "",
|
||||||
|
"user_settings": ""
|
||||||
},
|
},
|
||||||
"successess": {
|
"successess": {
|
||||||
"passwordchanged": ""
|
"passwordchanged": ""
|
||||||
|
|||||||
@@ -1,44 +1,43 @@
|
|||||||
import { Select } from "antd";
|
|
||||||
import i18n from "../translations/i18n";
|
import i18n from "../translations/i18n";
|
||||||
|
|
||||||
export default function CiecaSelect(parts = true, labor = true) {
|
export default function CiecaSelect(parts = true, labor = true) {
|
||||||
return (
|
const options = [];
|
||||||
<>
|
|
||||||
{labor && (
|
if (labor) {
|
||||||
<>
|
options.push(
|
||||||
<Select.Option value="LAA">{i18n.t("joblines.fields.lbr_types.LAA")}</Select.Option>
|
{ value: "LAA", label: i18n.t("joblines.fields.lbr_types.LAA") },
|
||||||
<Select.Option value="LAB">{i18n.t("joblines.fields.lbr_types.LAB")}</Select.Option>
|
{ value: "LAB", label: i18n.t("joblines.fields.lbr_types.LAB") },
|
||||||
<Select.Option value="LAD">{i18n.t("joblines.fields.lbr_types.LAD")}</Select.Option>
|
{ value: "LAD", label: i18n.t("joblines.fields.lbr_types.LAD") },
|
||||||
<Select.Option value="LAE">{i18n.t("joblines.fields.lbr_types.LAE")}</Select.Option>
|
{ value: "LAE", label: i18n.t("joblines.fields.lbr_types.LAE") },
|
||||||
<Select.Option value="LAF">{i18n.t("joblines.fields.lbr_types.LAF")}</Select.Option>
|
{ value: "LAF", label: i18n.t("joblines.fields.lbr_types.LAF") },
|
||||||
<Select.Option value="LAG">{i18n.t("joblines.fields.lbr_types.LAG")}</Select.Option>
|
{ value: "LAG", label: i18n.t("joblines.fields.lbr_types.LAG") },
|
||||||
<Select.Option value="LAM">{i18n.t("joblines.fields.lbr_types.LAM")}</Select.Option>
|
{ value: "LAM", label: i18n.t("joblines.fields.lbr_types.LAM") },
|
||||||
<Select.Option value="LAR">{i18n.t("joblines.fields.lbr_types.LAR")}</Select.Option>
|
{ value: "LAR", label: i18n.t("joblines.fields.lbr_types.LAR") },
|
||||||
<Select.Option value="LAS">{i18n.t("joblines.fields.lbr_types.LAS")}</Select.Option>
|
{ value: "LAS", label: i18n.t("joblines.fields.lbr_types.LAS") },
|
||||||
<Select.Option value="LAU">{i18n.t("joblines.fields.lbr_types.LAU")}</Select.Option>
|
{ value: "LAU", label: i18n.t("joblines.fields.lbr_types.LAU") },
|
||||||
<Select.Option value="LA1">{i18n.t("joblines.fields.lbr_types.LA1")}</Select.Option>
|
{ value: "LA1", label: i18n.t("joblines.fields.lbr_types.LA1") },
|
||||||
<Select.Option value="LA2">{i18n.t("joblines.fields.lbr_types.LA2")}</Select.Option>
|
{ value: "LA2", label: i18n.t("joblines.fields.lbr_types.LA2") },
|
||||||
<Select.Option value="LA3">{i18n.t("joblines.fields.lbr_types.LA3")}</Select.Option>
|
{ value: "LA3", label: i18n.t("joblines.fields.lbr_types.LA3") },
|
||||||
<Select.Option value="LA4">{i18n.t("joblines.fields.lbr_types.LA4")}</Select.Option>
|
{ value: "LA4", label: i18n.t("joblines.fields.lbr_types.LA4") }
|
||||||
</>
|
);
|
||||||
)}
|
}
|
||||||
{parts && (
|
|
||||||
<>
|
if (parts) {
|
||||||
<Select.Option value="PAA">{i18n.t("joblines.fields.part_types.PAA")}</Select.Option>
|
options.push(
|
||||||
<Select.Option value="PAC">{i18n.t("joblines.fields.part_types.PAC")}</Select.Option>
|
{ value: "PAA", label: i18n.t("joblines.fields.part_types.PAA") },
|
||||||
|
{ value: "PAC", label: i18n.t("joblines.fields.part_types.PAC") },
|
||||||
<Select.Option value="PAL">{i18n.t("joblines.fields.part_types.PAL")}</Select.Option>
|
{ value: "PAL", label: i18n.t("joblines.fields.part_types.PAL") },
|
||||||
<Select.Option value="PAG">{i18n.t("joblines.fields.part_types.PAG")}</Select.Option>
|
{ value: "PAG", label: i18n.t("joblines.fields.part_types.PAG") },
|
||||||
<Select.Option value="PAM">{i18n.t("joblines.fields.part_types.PAM")}</Select.Option>
|
{ value: "PAM", label: i18n.t("joblines.fields.part_types.PAM") },
|
||||||
<Select.Option value="PAP">{i18n.t("joblines.fields.part_types.PAP")}</Select.Option>
|
{ value: "PAP", label: i18n.t("joblines.fields.part_types.PAP") },
|
||||||
<Select.Option value="PAN">{i18n.t("joblines.fields.part_types.PAN")}</Select.Option>
|
{ value: "PAN", label: i18n.t("joblines.fields.part_types.PAN") },
|
||||||
<Select.Option value="PAO">{i18n.t("joblines.fields.part_types.PAO")}</Select.Option>
|
{ value: "PAO", label: i18n.t("joblines.fields.part_types.PAO") },
|
||||||
<Select.Option value="PAR">{i18n.t("joblines.fields.part_types.PAR")}</Select.Option>
|
{ value: "PAR", label: i18n.t("joblines.fields.part_types.PAR") },
|
||||||
<Select.Option value="PAS">{i18n.t("joblines.fields.part_types.PAS")}</Select.Option>
|
{ value: "PAS", label: i18n.t("joblines.fields.part_types.PAS") }
|
||||||
</>
|
);
|
||||||
)}
|
}
|
||||||
</>
|
|
||||||
);
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetPartTypeName(part_type) {
|
export function GetPartTypeName(part_type) {
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ export function DateFormatter(props) {
|
|||||||
return props.children ? dayjs(props.children).format(props.includeDay ? "ddd MM/DD/YYYY" : "MM/DD/YYYY") : null;
|
return props.children ? dayjs(props.children).format(props.includeDay ? "ddd MM/DD/YYYY" : "MM/DD/YYYY") : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DateTimeFormatter(props) {
|
export function DateTimeFormatter({ hideTime, ...props }) {
|
||||||
return props.children ? dayjs(props.children).format(props.format ? props.format : "MM/DD/YYYY hh:mm a") : null;
|
return props.children
|
||||||
|
? dayjs(props.children).format(props.format ? props.format : `MM/DD/YYYY${hideTime ? "" : " hh:mm a"}`)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DateTimeFormatterFunction(date) {
|
export function DateTimeFormatterFunction(date) {
|
||||||
@@ -17,11 +19,11 @@ export function TimeFormatter(props) {
|
|||||||
return props.children ? dayjs(props.children).format(props.format ? props.format : "hh:mm a") : null;
|
return props.children ? dayjs(props.children).format(props.format ? props.format : "hh:mm a") : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TimeAgoFormatter(props) {
|
export function TimeAgoFormatter({ removeAgoString = false, ...props }) {
|
||||||
const m = dayjs(props.children);
|
const m = dayjs(props.children);
|
||||||
return props.children ? (
|
return props.children ? (
|
||||||
<Tooltip placement="top" title={m.format("MM/DD/YYY hh:mm A")}>
|
<Tooltip placement="top" title={m.format("MM/DD/YYYY hh:mm A")}>
|
||||||
{m.fromNow()}
|
{m.fromNow(removeAgoString)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,7 +248,8 @@ const client = new ApolloClient({
|
|||||||
watchQuery: {
|
watchQuery: {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
errorPolicy: "ignore"
|
errorPolicy: "ignore",
|
||||||
|
notifyOnNetworkStatusChange: false
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ export async function generateTemplate(
|
|||||||
if (templateQueryToExecute) {
|
if (templateQueryToExecute) {
|
||||||
const { data } = await client.query({
|
const { data } = await client.query({
|
||||||
query: gql(finalQuery),
|
query: gql(finalQuery),
|
||||||
variables: { ...templateObject.variables }
|
variables: { ...templateObject.variables },
|
||||||
|
fetchPolicy: "no-cache"
|
||||||
});
|
});
|
||||||
contextData = data;
|
contextData = data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
localstack:
|
localstack:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
aws-cli:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
ports:
|
ports:
|
||||||
- "4001:4000" # Different external port for local access
|
- "4001:4000" # Different external port for local access
|
||||||
volumes:
|
volumes:
|
||||||
@@ -65,8 +63,6 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
localstack:
|
localstack:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
aws-cli:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
ports:
|
ports:
|
||||||
- "4002:4000" # Different external port for local access
|
- "4002:4000" # Different external port for local access
|
||||||
volumes:
|
volumes:
|
||||||
@@ -92,8 +88,6 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
localstack:
|
localstack:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
aws-cli:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
ports:
|
ports:
|
||||||
- "4003:4000" # Different external port for local access
|
- "4003:4000" # Different external port for local access
|
||||||
volumes:
|
volumes:
|
||||||
@@ -156,23 +150,18 @@ services:
|
|||||||
|
|
||||||
# LocalStack
|
# LocalStack
|
||||||
localstack:
|
localstack:
|
||||||
image: localstack/localstack
|
image: localstack/localstack:4.13.1
|
||||||
container_name: localstack
|
container_name: localstack
|
||||||
hostname: localstack
|
hostname: localstack
|
||||||
networks:
|
networks:
|
||||||
- redis-cluster-net
|
- redis-cluster-net
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./certs:/tmp/certs:ro # only if your script reads /tmp/certs/...
|
||||||
|
- ./localstack/init:/etc/localstack/init/ready.d:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
environment:
|
env_file:
|
||||||
- SERVICES=s3,ses,secretsmanager,cloudwatch,logs
|
- .env.localstack.docker
|
||||||
- DEBUG=0
|
|
||||||
- AWS_ACCESS_KEY_ID=test
|
|
||||||
- AWS_SECRET_ACCESS_KEY=test
|
|
||||||
- AWS_DEFAULT_REGION=ca-central-1
|
|
||||||
- EXTRA_CORS_ALLOWED_HEADERS=Authorization,Content-Type
|
|
||||||
- EXTRA_CORS_ALLOWED_ORIGINS=*
|
|
||||||
- EXTRA_CORS_EXPOSE_HEADERS=Authorization,Content-Type
|
|
||||||
ports:
|
ports:
|
||||||
- "4566:4566"
|
- "4566:4566"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -182,36 +171,6 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
start_period: 20s
|
start_period: 20s
|
||||||
|
|
||||||
# AWS-CLI
|
|
||||||
aws-cli:
|
|
||||||
image: amazon/aws-cli
|
|
||||||
container_name: aws-cli
|
|
||||||
hostname: aws-cli
|
|
||||||
networks:
|
|
||||||
- redis-cluster-net
|
|
||||||
depends_on:
|
|
||||||
localstack:
|
|
||||||
condition: service_healthy
|
|
||||||
volumes:
|
|
||||||
- './localstack:/tmp/localstack'
|
|
||||||
- './certs:/tmp/certs'
|
|
||||||
environment:
|
|
||||||
- AWS_ACCESS_KEY_ID=test
|
|
||||||
- AWS_SECRET_ACCESS_KEY=test
|
|
||||||
- AWS_DEFAULT_REGION=ca-central-1
|
|
||||||
entrypoint: /bin/sh -c
|
|
||||||
command: >
|
|
||||||
"
|
|
||||||
aws --endpoint-url=http://localstack:4566 ses verify-domain-identity --domain imex.online --region ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 ses verify-email-identity --email-address noreply@imex.online --region ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/io-ftp-test.key
|
|
||||||
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket rome-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket rps-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
|
||||||
"
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
redis-cluster-net:
|
redis-cluster-net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
@@ -68,23 +68,18 @@ services:
|
|||||||
# LocalStack: Used to emulate AWS services locally, currently setup for SES
|
# LocalStack: Used to emulate AWS services locally, currently setup for SES
|
||||||
# Notes: Set the ENV Debug to 1 for additional logging
|
# Notes: Set the ENV Debug to 1 for additional logging
|
||||||
localstack:
|
localstack:
|
||||||
image: localstack/localstack
|
image: localstack/localstack:4.13.1
|
||||||
container_name: localstack
|
container_name: localstack
|
||||||
hostname: localstack
|
hostname: localstack
|
||||||
networks:
|
networks:
|
||||||
- redis-cluster-net
|
- redis-cluster-net
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./certs:/tmp/certs:ro # only if your script reads /tmp/certs/...
|
||||||
|
- ./localstack/init:/etc/localstack/init/ready.d:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
environment:
|
env_file:
|
||||||
- SERVICES=s3,ses,secretsmanager,cloudwatch,logs
|
- .env.localstack.docker
|
||||||
- DEBUG=0
|
|
||||||
- AWS_ACCESS_KEY_ID=test
|
|
||||||
- AWS_SECRET_ACCESS_KEY=test
|
|
||||||
- AWS_DEFAULT_REGION=ca-central-1
|
|
||||||
- EXTRA_CORS_ALLOWED_HEADERS=Authorization,Content-Type
|
|
||||||
- EXTRA_CORS_ALLOWED_ORIGINS=*
|
|
||||||
- EXTRA_CORS_EXPOSE_HEADERS=Authorization,Content-Type
|
|
||||||
ports:
|
ports:
|
||||||
- "4566:4566"
|
- "4566:4566"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -94,38 +89,6 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
start_period: 20s
|
start_period: 20s
|
||||||
|
|
||||||
# AWS-CLI - Used in conjunction with LocalStack to set required permission to send emails
|
|
||||||
aws-cli:
|
|
||||||
image: amazon/aws-cli
|
|
||||||
container_name: aws-cli
|
|
||||||
hostname: aws-cli
|
|
||||||
networks:
|
|
||||||
- redis-cluster-net
|
|
||||||
depends_on:
|
|
||||||
localstack:
|
|
||||||
condition: service_healthy
|
|
||||||
volumes:
|
|
||||||
- './localstack:/tmp/localstack'
|
|
||||||
- './certs:/tmp/certs'
|
|
||||||
|
|
||||||
environment:
|
|
||||||
- AWS_ACCESS_KEY_ID=test
|
|
||||||
- AWS_SECRET_ACCESS_KEY=test
|
|
||||||
- AWS_DEFAULT_REGION=ca-central-1
|
|
||||||
entrypoint: /bin/sh -c
|
|
||||||
command: >
|
|
||||||
"
|
|
||||||
aws --endpoint-url=http://localstack:4566 ses verify-domain-identity --domain imex.online --region ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 ses verify-email-identity --email-address noreply@imex.online --region ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/io-ftp-test.key
|
|
||||||
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-job-totals --create-bucket-configuration LocationConstraint=ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket parts-estimates --create-bucket-configuration LocationConstraint=ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket rome-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket rps-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
|
||||||
"
|
|
||||||
# Node App: The Main IMEX API
|
# Node App: The Main IMEX API
|
||||||
node-app:
|
node-app:
|
||||||
build:
|
build:
|
||||||
@@ -145,8 +108,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
localstack:
|
localstack:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
aws-cli:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
ports:
|
ports:
|
||||||
- "4000:4000"
|
- "4000:4000"
|
||||||
- "9229:9229"
|
- "9229:9229"
|
||||||
|
|||||||
@@ -947,6 +947,7 @@
|
|||||||
- carfax_exclude
|
- carfax_exclude
|
||||||
- cdk_configuration
|
- cdk_configuration
|
||||||
- cdk_dealerid
|
- cdk_dealerid
|
||||||
|
- chatter_company_id
|
||||||
- chatterid
|
- chatterid
|
||||||
- city
|
- city
|
||||||
- claimscorpid
|
- claimscorpid
|
||||||
@@ -1063,6 +1064,7 @@
|
|||||||
- bill_allow_post_to_closed
|
- bill_allow_post_to_closed
|
||||||
- bill_tax_rates
|
- bill_tax_rates
|
||||||
- cdk_configuration
|
- cdk_configuration
|
||||||
|
- chatter_company_id
|
||||||
- city
|
- city
|
||||||
- country
|
- country
|
||||||
- created_at
|
- created_at
|
||||||
@@ -3702,7 +3704,9 @@
|
|||||||
- ded_status
|
- ded_status
|
||||||
- deliverchecklist
|
- deliverchecklist
|
||||||
- depreciation_taxes
|
- depreciation_taxes
|
||||||
|
- dms_advisor_id
|
||||||
- dms_allocation
|
- dms_allocation
|
||||||
|
- dms_customer_id
|
||||||
- dms_id
|
- dms_id
|
||||||
- driveable
|
- driveable
|
||||||
- employee_body
|
- employee_body
|
||||||
@@ -3983,7 +3987,9 @@
|
|||||||
- ded_status
|
- ded_status
|
||||||
- deliverchecklist
|
- deliverchecklist
|
||||||
- depreciation_taxes
|
- depreciation_taxes
|
||||||
|
- dms_advisor_id
|
||||||
- dms_allocation
|
- dms_allocation
|
||||||
|
- dms_customer_id
|
||||||
- dms_id
|
- dms_id
|
||||||
- driveable
|
- driveable
|
||||||
- employee_body
|
- employee_body
|
||||||
@@ -4276,7 +4282,9 @@
|
|||||||
- ded_status
|
- ded_status
|
||||||
- deliverchecklist
|
- deliverchecklist
|
||||||
- depreciation_taxes
|
- depreciation_taxes
|
||||||
|
- dms_advisor_id
|
||||||
- dms_allocation
|
- dms_allocation
|
||||||
|
- dms_customer_id
|
||||||
- dms_id
|
- dms_id
|
||||||
- driveable
|
- driveable
|
||||||
- employee_body
|
- employee_body
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."bodyshops" add column "chatter_company_id" text
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."bodyshops" add column "chatter_company_id" text
|
||||||
|
null;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."jobs" add column "dms_customer_id" text
|
||||||
|
-- null;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user