From b8246e03c157f014063216630c31653ec14e9b56 Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 25 Mar 2026 10:16:48 -0400 Subject: [PATCH] IO-3624 Polish config empty states and admin cards --- .../config-list-empty-state.component.jsx | 11 + .../inline-form-row-title.utils.js | 52 +- .../layout-form-row.styles.scss | 33 + .../shop-employees-form.component.jsx | 272 +- .../shop-employees-list.component.jsx | 98 +- .../shop-info/shop-info.component.jsx | 10 +- .../shop-info/shop-info.general.component.jsx | 2297 +++++------ .../shop-info/shop-info.intake.component.jsx | 586 +-- .../shop-info.laborrates.component.jsx | 706 ++-- .../shop-info/shop-info.parts-scan.jsx | 404 +- ...p-info.responsibilitycenters.component.jsx | 3609 ++++++++--------- .../shop-info.rostatus.component.jsx | 203 +- .../shop-info.scheduling.component.jsx | 485 +-- .../shop-info.speedprint.component.jsx | 247 +- .../shop-info.task-presets.component.jsx | 397 +- .../shop-employee-teams.form.component.jsx | 356 +- ...hop-employee-teams.form.component.test.jsx | 7 +- .../shop-teams/shop-employee-teams.list.jsx | 96 +- .../shop-users/shop-users.component.jsx | 5 +- client/src/translations/en_us/common.json | 23 +- client/src/translations/es/common.json | 23 +- client/src/translations/fr/common.json | 23 +- 22 files changed, 4976 insertions(+), 4967 deletions(-) create mode 100644 client/src/components/layout-form-row/config-list-empty-state.component.jsx diff --git a/client/src/components/layout-form-row/config-list-empty-state.component.jsx b/client/src/components/layout-form-row/config-list-empty-state.component.jsx new file mode 100644 index 000000000..fc0f41570 --- /dev/null +++ b/client/src/components/layout-form-row/config-list-empty-state.component.jsx @@ -0,0 +1,11 @@ +import { useTranslation } from "react-i18next"; + +export default function ConfigListEmptyState({ actionLabel, minHeight = 96 }) { + const { t } = useTranslation(); + + return ( +
+ {t("general.labels.click_to_begin", { action: actionLabel })} +
+ ); +} diff --git a/client/src/components/layout-form-row/inline-form-row-title.utils.js b/client/src/components/layout-form-row/inline-form-row-title.utils.js index df659026a..1647422c1 100644 --- a/client/src/components/layout-form-row/inline-form-row-title.utils.js +++ b/client/src/components/layout-form-row/inline-form-row-title.utils.js @@ -1,15 +1,19 @@ export const inlineFormRowTitleStyles = Object.freeze({ input: Object.freeze({ - background: "var(--imex-form-title-input-bg)", - border: "1px solid var(--imex-form-title-input-border)", - borderRadius: 6, - paddingInline: 10, - paddingBlock: 4, - lineHeight: 1.3 + background: "transparent", + border: "none", + borderRadius: 0, + boxShadow: "none", + paddingInline: 0, + paddingBlock: 0, + lineHeight: 1.35, + flex: "1 1 auto", + minWidth: 0, + width: "100%" }), row: Object.freeze({ display: "flex", - gap: 4, + gap: 6, flexWrap: "wrap", alignItems: "center", width: "100%", @@ -18,25 +22,37 @@ export const inlineFormRowTitleStyles = Object.freeze({ group: Object.freeze({ display: "flex", alignItems: "center", - gap: 6, - minWidth: 0 + gap: 8, + paddingInline: 8, + paddingBlock: 4, + borderRadius: 10, + border: "1px solid var(--imex-form-title-group-border)", + background: "var(--imex-form-title-group-bg)", + minWidth: 0, + flex: "1 1 0" }), label: Object.freeze({ - color: "var(--ant-color-text)", - fontSize: "var(--ant-font-size)", - fontWeight: 500, - whiteSpace: "nowrap" + color: "var(--ant-color-text-secondary)", + fontSize: 12, + fontWeight: 600, + lineHeight: 1, + whiteSpace: "nowrap", + paddingInline: 6, + paddingBlock: 3, + borderRadius: 999, + border: "1px solid var(--imex-form-title-label-border)", + background: "var(--imex-form-title-label-bg)" }), handle: Object.freeze({ color: "var(--ant-color-text-tertiary)", fontSize: 14, flex: "0 0 auto", - marginRight: 4 + marginRight: 2 }), separator: Object.freeze({ width: 1, - height: 18, - background: "color-mix(in srgb, var(--imex-form-surface-border) 78%, transparent)", + height: 16, + background: "color-mix(in srgb, var(--imex-form-surface-border) 58%, transparent)", borderRadius: 999, flex: "0 0 auto", marginInline: 2 @@ -52,6 +68,10 @@ export const inlineFormRowTitleStyles = Object.freeze({ export const INLINE_TITLE_INPUT_STYLE = inlineFormRowTitleStyles.input; export const INLINE_TITLE_ROW_STYLE = inlineFormRowTitleStyles.row; export const INLINE_TITLE_GROUP_STYLE = inlineFormRowTitleStyles.group; +export const INLINE_TITLE_SWITCH_GROUP_STYLE = Object.freeze({ + ...inlineFormRowTitleStyles.group, + flex: "0 0 auto" +}); export const INLINE_TITLE_LABEL_STYLE = inlineFormRowTitleStyles.label; export const INLINE_TITLE_HANDLE_STYLE = inlineFormRowTitleStyles.handle; export const INLINE_TITLE_SEPARATOR_STYLE = inlineFormRowTitleStyles.separator; diff --git a/client/src/components/layout-form-row/layout-form-row.styles.scss b/client/src/components/layout-form-row/layout-form-row.styles.scss index 4284c5223..3e8674362 100644 --- a/client/src/components/layout-form-row/layout-form-row.styles.scss +++ b/client/src/components/layout-form-row/layout-form-row.styles.scss @@ -15,6 +15,10 @@ --imex-form-surface-border: #d9d9d9; /* matches AntD-ish border */ --imex-form-title-input-bg: rgba(255, 255, 255, 0.96); --imex-form-title-input-border: rgba(0, 0, 0, 0.08); + --imex-form-title-group-bg: rgba(255, 255, 255, 0.72); + --imex-form-title-group-border: rgba(0, 0, 0, 0.08); + --imex-form-title-label-bg: rgba(0, 0, 0, 0.04); + --imex-form-title-label-border: rgba(0, 0, 0, 0.06); } /* Pick the selector that matches your app and remove the rest */ @@ -24,6 +28,10 @@ html[data-theme="dark"] { --imex-form-surface-border: rgba(5, 5, 5, 0.12); --imex-form-title-input-bg: rgba(255, 255, 255, 0.12); --imex-form-title-input-border: rgba(255, 255, 255, 0.2); + --imex-form-title-group-bg: rgba(255, 255, 255, 0.08); + --imex-form-title-group-border: rgba(255, 255, 255, 0.16); + --imex-form-title-label-bg: rgba(255, 255, 255, 0.06); + --imex-form-title-label-border: rgba(255, 255, 255, 0.12); } .imex-form-row { @@ -114,6 +122,20 @@ html[data-theme="dark"] { background: var(--imex-form-surface); } + .ant-card-actions { + background: var(--imex-form-surface-head); + border-top-color: var(--imex-form-surface-border); + } + + .ant-card-actions > li { + margin: 10px 0; + padding-inline: 12px; + } + + .ant-card-actions .ant-btn { + width: 100%; + } + .ant-form-item:last-child { margin-bottom: 4px; } @@ -156,3 +178,14 @@ html[data-theme="dark"] { } } } + +.imex-form-row-empty-state { + display: flex; + align-items: center; + justify-content: center; + padding: 24px 16px; + text-align: center; + color: var(--ant-color-text-description); + font-size: var(--ant-font-size); + line-height: 1.5; +} diff --git a/client/src/components/shop-employees/shop-employees-form.component.jsx b/client/src/components/shop-employees/shop-employees-form.component.jsx index 89cc115ff..cd677e6d1 100644 --- a/client/src/components/shop-employees/shop-employees-form.component.jsx +++ b/client/src/components/shop-employees/shop-employees-form.component.jsx @@ -27,6 +27,7 @@ import dayjs from "../../utils/day"; import AlertComponent from "../alert/alert.component"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { INLINE_TITLE_GROUP_STYLE, @@ -35,6 +36,7 @@ import { INLINE_TITLE_LABEL_STYLE, INLINE_TITLE_ROW_STYLE, INLINE_TITLE_SEPARATOR_STYLE, + INLINE_TITLE_SWITCH_GROUP_STYLE, INLINE_TITLE_TEXT_STYLE } from "../layout-form-row/inline-form-row-title.utils.js"; import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component"; @@ -166,6 +168,8 @@ export function ShopEmployeesFormComponent({ bodyshop }) { key: "actions", render: (text, record) => ( } > @@ -229,8 +233,7 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
{t("employees.labels.active")}
@@ -241,8 +244,7 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
{t("employees.fields.flat_rate")}
@@ -396,147 +398,141 @@ export function ShopEmployeesFormComponent({ bodyshop }) { - - - {(fields, { add, remove, move }) => { - return ( + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }} + id="add-employee-rate-button" + > + {t("employees.actions.addrate")} + + ]} + >
- {fields.map((field, index) => { - return ( - - - -
-
{t("employees.fields.cost_center")}
- - ({ + value: c.name, + label: c.name + }))) + ]} + style={{ width: "100%" }} + styles={{ + selector: INLINE_TITLE_INPUT_STYLE + }} + /> + +
-
-
-
{t("employees.fields.rate")}
- - - -
-
- } - wrapTitle - extra={ - - - + } + wrapTitle + extra={ + +
- ); - }} - - + + ); + }} + - -
- -
- + {(data?.employees_by_pk?.employee_vacations ?? []).length === 0 ? ( + + ) : ( +
+
-
+ )} ); diff --git a/client/src/components/shop-employees/shop-employees-list.component.jsx b/client/src/components/shop-employees/shop-employees-list.component.jsx index e6ecda6cf..787554877 100644 --- a/client/src/components/shop-employees/shop-employees-list.component.jsx +++ b/client/src/components/shop-employees/shop-employees-list.component.jsx @@ -4,6 +4,8 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate } from "react-router-dom"; import { alphaSort } from "../../utils/sorters"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; +import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import ResponsiveTable from "../responsive-table/responsive-table.component"; export default function ShopEmployeesListComponent({ loading, employees }) { @@ -16,13 +18,28 @@ export default function ShopEmployeesListComponent({ loading, employees }) { filteredInfo: { text: "" } }); + const navigateToEmployee = (employeeId) => { + history({ + search: queryString.stringify({ + ...search, + employeeId + }) + }); + }; + + const clearEmployeeSelection = () => { + const { employeeId, ...nextSearch } = search; + void employeeId; + history({ + search: queryString.stringify(nextSearch) + }); + }; + const handleOnRowClick = (record) => { if (record) { - search.employeeId = record.id; - history({ search: queryString.stringify(search) }); + navigateToEmployee(record.id); } else { - delete search.employeeId; - history({ search: queryString.stringify(search) }); + clearEmployeeSelection(); } }; const handleTableChange = (pagination, filters, sorter) => { @@ -89,44 +106,39 @@ export default function ShopEmployeesListComponent({ loading, employees }) { } ]; return ( -
- { - return ( - - ); - }} - loading={loading} - pagination={{ placement: "top" }} - columns={columns} - mobileColumnKeys={["employee_number", "employee_name", "active"]} - rowKey="id" - dataSource={employees} - rowSelection={{ - onSelect: (props) => { - search.employeeId = props.id; - history({ search: queryString.stringify(search) }); - }, - type: "radio", - selectedRowKeys: [search.employeeId] - }} - onChange={handleTableChange} - onRow={(record) => { - return { - onClick: () => { - handleOnRowClick(record); - } - }; - }} - /> -
+ navigateToEmployee("new")}> + {t("employees.actions.new")} + + ]} + > + {employees.length === 0 ? ( + + ) : ( + navigateToEmployee(props.id), + type: "radio", + selectedRowKeys: [search.employeeId] + }} + onChange={handleTableChange} + onRow={(record) => { + return { + onClick: () => { + handleOnRowClick(record); + } + }; + }} + /> + )} + ); } diff --git a/client/src/components/shop-info/shop-info.component.jsx b/client/src/components/shop-info/shop-info.component.jsx index c74427724..ee7e40bd9 100644 --- a/client/src/components/shop-info/shop-info.component.jsx +++ b/client/src/components/shop-info/shop-info.component.jsx @@ -163,8 +163,14 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) { } extra={ - } > diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index 00006c1f2..55ce20351 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -8,6 +8,7 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component" import FormItemEmail from "../form-items-formatted/email-form-item.component"; import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { INLINE_TITLE_GROUP_STYLE, @@ -15,7 +16,8 @@ import { INLINE_TITLE_INPUT_STYLE, INLINE_TITLE_LABEL_STYLE, INLINE_TITLE_ROW_STYLE, - INLINE_TITLE_SEPARATOR_STYLE + INLINE_TITLE_SEPARATOR_STYLE, + INLINE_TITLE_SWITCH_GROUP_STYLE } from "../layout-form-row/inline-form-row-title.utils.js"; const timeZonesList = Intl.supportedValuesOf("timeZone"); @@ -28,6 +30,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral); export function ShopInfoGeneral({ form }) { const { t } = useTranslation(); + const buildSectionActionButton = (key, label, onClick, id) => ( + + ); + const renderListOrEmpty = (fields, actionLabel, renderItems) => + fields.length === 0 ? : renderItems(); return (
@@ -449,715 +458,22 @@ export function ShopInfoGeneral({ form }) { - - - {(fields, { add, remove, move }) => { - return ( + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }) + ]} + >
- {fields.map((field, index) => { - return ( - - - -
-
{t("bodyshop.fields.messaginglabel_short")}
- - - -
-
-
-
{t("bodyshop.fields.messagingtext_short")}
- - - -
-
- } - extra={ - - -
-
- ); - }} -
-
- - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { - return ( - - - -
-
{t("bodyshop.fields.noteslabel_short")}
- - - -
-
-
-
{t("bodyshop.fields.notestext_short")}
- - - -
-
- } - extra={ - - -
-
- ); - }} -
-
- - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { - return ( - - - -
-
{t("bodyshop.fields.partslocation")}
- - - -
-
- } - extra={ - - - -
- ); - }} - - - - {/*Start Insurance Provider Row */} - {t("bodyshop.labels.insurancecos")}} - id="insurancecos" - > - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { - return ( - - - -
-
{t("bodyshop.fields.md_ins_co.name")}
- ({ - validator: async (_, value) => { - const normalizedValue = (value ?? "").toString().trim().toLowerCase(); - if (!normalizedValue) return Promise.resolve(); - - const list = getFieldValue(["md_ins_cos"]) || []; - const normalizedNames = list - .map((c) => (c?.name ?? "").toString().trim().toLowerCase()) - .filter(Boolean); - - const count = normalizedNames.filter((n) => n === normalizedValue).length; - - if (count > 1) { - throw new Error(t("bodyshop.errors.duplicate_insurance_company")); - } - - return Promise.resolve(); - } - }) - ]} - > - - -
-
-
-
{t("bodyshop.fields.md_ins_co.private")}
- - - -
-
- } - wrapTitle - extra={ - - -
-
- ); - }} -
-
- {/*End Insurance Provider Row */} - - - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { - return ( - - - -
-
{t("jobs.fields.est_ct_fn_short")}
- - - -
-
-
-
{t("jobs.fields.est_ct_ln_short")}
- - - -
-
-
-
{t("jobs.fields.est_co_nm_short")}
- - - -
-
- } - wrapTitle - extra={ - - - -
- ); - }} - -
- - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { - return ( - - - -
-
{t("jobs.fields.ins_ct_fn_short")}
- - - -
-
-
-
{t("jobs.fields.ins_ct_ln_short")}
- - - -
-
- } - wrapTitle - extra={ - - -
-
- ); - }} -
-
- null}> - - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { + {renderListOrEmpty(fields, t("bodyshop.actions.add_messaging_preset"), () => + fields.map((field, index) => { return ( -
+
{t("bodyshop.fields.messaginglabel_short")}
+ + + +
+
+ } + extra={ + +
+
+ ); + }} + + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }) + ]} + > +
+ {renderListOrEmpty(fields, t("bodyshop.actions.add_note_preset"), () => + fields.map((field, index) => { + return ( + + + +
+
{t("bodyshop.fields.noteslabel_short")}
+ + + +
+
+ } + extra={ + +
+
+ ); + }} + + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }) + ]} + > +
+ {renderListOrEmpty(fields, t("bodyshop.actions.addpartslocation"), () => + fields.map((field, index) => { + return ( + + + +
+
{t("bodyshop.fields.partslocation")}
+ + + +
+
+ } + extra={ + +
+ + ); + }} + + + {/*Start Insurance Provider Row */} + + {(fields, { add, remove, move }) => { + return ( + {t("bodyshop.labels.insurancecos")}} + id="insurancecos" + actions={[ + buildSectionActionButton( + "add-insurance-company", + t("bodyshop.actions.add_insurance_company"), + () => { + add(); + }, + "insurancecos-add-button" + ) + ]} + > +
+ {renderListOrEmpty(fields, t("bodyshop.actions.add_insurance_company"), () => + fields.map((field, index) => { + return ( + + + +
+
{t("bodyshop.fields.md_ins_co.name")}
+ ({ + validator: async (_, value) => { + const normalizedValue = (value ?? "").toString().trim().toLowerCase(); + if (!normalizedValue) return Promise.resolve(); + + const list = getFieldValue(["md_ins_cos"]) || []; + const normalizedNames = list + .map((c) => (c?.name ?? "").toString().trim().toLowerCase()) + .filter(Boolean); + + const count = normalizedNames.filter((n) => n === normalizedValue).length; + + if (count > 1) { + throw new Error(t("bodyshop.errors.duplicate_insurance_company")); + } + + return Promise.resolve(); + } + }) + ]} + > + + +
+
+
+
{t("bodyshop.fields.md_ins_co.private")}
+ + + +
+
+ } + wrapTitle + extra={ + +
+
+ ); + }} +
+ {/*End Insurance Provider Row */} + + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }) + ]} + > +
+ {renderListOrEmpty(fields, t("bodyshop.actions.add_estimator"), () => + fields.map((field, index) => { + return ( + + + +
+
{t("jobs.fields.est_ct_fn_short")}
+ + + +
+
+
+
{t("jobs.fields.est_ct_ln_short")}
+ + + +
+
+
+
{t("jobs.fields.est_co_nm_short")}
+ + + +
+
+ } + wrapTitle + extra={ + +
+
+ ); + }} + + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }) + ]} + > +
+ {renderListOrEmpty(fields, t("bodyshop.actions.add_adjuster"), () => + fields.map((field, index) => { + return ( + + + +
+
{t("jobs.fields.ins_ct_fn_short")}
+ + + +
+
+
+
{t("jobs.fields.ins_ct_ln_short")}
+ + + +
+
+ } + wrapTitle + extra={ + +
+
+ ); + }} +
+ null}> + + {(fields, { add, remove, move }) => { + return ( + { + add(); + } + ) + ]} + > +
+ {renderListOrEmpty(fields, t("bodyshop.actions.add_courtesy_car_rate_preset"), () => + fields.map((field, index) => { + return ( + + + +
+
{t("general.labels.label")}
+ + + +
+
+ } + wrapTitle + extra={ + +
+
+ ); + }} +
+ + + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }) + ]} + > +
+ {renderListOrEmpty(fields, t("bodyshop.actions.add_jobline_preset"), () => + fields.map((field, index) => { + return ( + + + +
+
{t("general.labels.label")}
+ + + +
+
+
+
{t("joblines.fields.line_desc")}
+ + + +
+
+ } + wrapTitle + extra={ + +
+
+ ); + }} +
+ + {(fields, { add, remove, move }) => { + return ( + { + add(); + } + ) + ]} + > +
+ {renderListOrEmpty(fields, t("bodyshop.actions.add_parts_order_comment"), () => + fields.map((field, index) => { + return ( + + + +
{t("general.labels.label")}
- -
- ); - }} - -
- - - - - {(fields, { add, remove, move }) => { - return ( + }) + )} +
+
+ ); + }} +
+ + {(fields, { add, remove, move }) => { + return ( + { + add(); + }) + ]} + >
- {fields.map((field, index) => { - return ( - - - -
-
{t("general.labels.label")}
- - - + {renderListOrEmpty(fields, t("bodyshop.actions.add_to_email_preset"), () => + fields.map((field, index) => { + return ( + + + +
+
{t("general.labels.label")}
+ + + +
-
-
-
{t("joblines.fields.line_desc")}
- - - -
-
- } - wrapTitle - extra={ - - -
+ style={{ marginBottom: 0 }} + > + + + + + ); + }) + )}
- ); - }} -
- - - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { - return ( - - - -
-
{t("general.labels.label")}
- - - -
-
-
-
{t("parts_orders.fields.comments")}
- - - -
-
- } - wrapTitle - extra={ - - -
-
- ); - }} -
-
- - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { - return ( - - - -
-
{t("general.labels.label")}
- - - -
-
-
-
{t("bodyshop.labels.md_to_emails_emails")}
- - - -
-
- } - wrapTitle - extra={ - - -
-
- ); - }} -
-
+ + ); + }} +
); } diff --git a/client/src/components/shop-info/shop-info.intake.component.jsx b/client/src/components/shop-info/shop-info.intake.component.jsx index 4a938f873..182b4c878 100644 --- a/client/src/components/shop-info/shop-info.intake.component.jsx +++ b/client/src/components/shop-info/shop-info.intake.component.jsx @@ -5,6 +5,7 @@ import styled from "styled-components"; import { TemplateList } from "../../utils/TemplateConstants"; import ConfigFormTypes from "../config-form-components/config-form-types"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { INLINE_TITLE_GROUP_STYLE, @@ -12,7 +13,8 @@ import { INLINE_TITLE_INPUT_STYLE, INLINE_TITLE_LABEL_STYLE, INLINE_TITLE_ROW_STYLE, - INLINE_TITLE_SEPARATOR_STYLE + INLINE_TITLE_SEPARATOR_STYLE, + INLINE_TITLE_SWITCH_GROUP_STYLE } from "../layout-form-row/inline-form-row-title.utils.js"; const SelectorDiv = styled.div` @@ -87,320 +89,318 @@ export default function ShopInfoIntakeChecklistComponent({ form }) { - - - {(fields, { add, remove, move }) => { - return ( + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }} + > + {t("bodyshop.actions.add_intake_checklist_item")} + + ]} + >
- {fields.map((field, index) => { - return ( - - - -
-
{t("jobs.fields.intake.name")}
- - - -
-
-
-
{t("jobs.fields.intake.required")}
- - - -
-
- } - wrapTitle - extra={ - -
+
+ ); + }} +
+ + {(fields, { add, remove, move }) => { + return ( + { + add(); + }} + > + {t("bodyshop.actions.add_delivery_checklist_item")} + + ]} + > +
+ {fields.length === 0 ? ( + + ) : ( + fields.map((field, index) => { + return ( + + + +
+
{t("jobs.fields.intake.name")}
- + - - ); - }} - - - - ); - })} - - - -
- ); - }} - -
- - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { - return ( - - - -
-
{t("jobs.fields.intake.name")}
- - - +
+
+
+
{t("jobs.fields.intake.required")}
+ + + +
-
-
-
{t("jobs.fields.intake.required")}
- - - -
-
- } - wrapTitle - extra={ - - -
+ + {() => { + if (form.getFieldValue(["deliverchecklist", "form", index, "type"]) !== "slider") + return null; + return ( + <> + + + + + + + + ); + }} + + + + ); + }) + )}
- ); - }} -
-
+ + ); + }} +
); } diff --git a/client/src/components/shop-info/shop-info.laborrates.component.jsx b/client/src/components/shop-info/shop-info.laborrates.component.jsx index bb61db15b..3e1b16705 100644 --- a/client/src/components/shop-info/shop-info.laborrates.component.jsx +++ b/client/src/components/shop-info/shop-info.laborrates.component.jsx @@ -3,6 +3,7 @@ import { Button, Form, Input, Space } from "antd"; import { useTranslation } from "react-i18next"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { INLINE_TITLE_GROUP_STYLE, @@ -25,361 +26,364 @@ export default function ShopInfoLaborRates() {
- - - {(fields, { add, remove, move }) => { - return ( + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }} + > + {t("bodyshop.actions.newlaborrate")} + + ]} + >
- {fields.map((field, index) => { - return ( - - - -
-
{t("jobs.fields.labor_rate_desc")}
- - - + {fields.length === 0 ? ( + + ) : ( + fields.map((field, index) => { + return ( + + + +
+
{t("jobs.fields.labor_rate_desc")}
+ + + +
-
- } - wrapTitle - extra={ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + // + // + // + // + // + // + } + + + + + + +
+ + ); + }) + )}
- ); - }} - - + + ); + }} + ); } diff --git a/client/src/components/shop-info/shop-info.parts-scan.jsx b/client/src/components/shop-info/shop-info.parts-scan.jsx index 3f7e94906..64a038479 100644 --- a/client/src/components/shop-info/shop-info.parts-scan.jsx +++ b/client/src/components/shop-info/shop-info.parts-scan.jsx @@ -3,6 +3,7 @@ import { Button, Col, Form, Input, Row, Select, Space, Switch } from "antd"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { INLINE_TITLE_GROUP_STYLE, @@ -10,7 +11,8 @@ import { INLINE_TITLE_INPUT_STYLE, INLINE_TITLE_LABEL_STYLE, INLINE_TITLE_ROW_STYLE, - INLINE_TITLE_SEPARATOR_STYLE + INLINE_TITLE_SEPARATOR_STYLE, + INLINE_TITLE_SWITCH_GROUP_STYLE } from "../layout-form-row/inline-form-row-title.utils.js"; import i18n from "i18next"; @@ -76,229 +78,221 @@ export default function ShopInfoPartsScan({ form }) { return (
- - - {(fields, { add, remove, move }) => ( + + {(fields, { add, remove, move }) => ( + + add({ + field: "line_desc", + operation: "contains", + mark_critical: true, + caseInsensitive: true + }) + } + > + {t("bodyshop.actions.addpartsrule")} + + ]} + >
- {fields.map((field, index) => { - const selectedField = watchedFields?.[index]?.field || "line_desc"; - const fieldType = getFieldType(selectedField); + {fields.length === 0 ? ( + + ) : ( + fields.map((field, index) => { + const selectedField = watchedFields?.[index]?.field || "line_desc"; + const fieldType = getFieldType(selectedField); - return ( - - - -
-
{t("bodyshop.fields.md_parts_scan.field")}
+ return ( + + + +
+
{t("bodyshop.fields.md_parts_scan.field")}
+ + + + + )} + + {/* Value */} + {fieldType && ( + + + {fieldType === "predefined" ? ( + + )} + + + )} + + {/* Update Field */} + + - )} - - {/* Value */} - {fieldType && ( - - - {fieldType === "predefined" ? ( - - )} - - - )} - - {/* Update Field */} - - - - - - - - - ); - })} - - - - + + + + ); + }) + )}
- )} - -
+ + )} +
); } diff --git a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx index 48550c24c..965c406e3 100644 --- a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx +++ b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx @@ -12,6 +12,7 @@ import DataLabel from "../data-label/data-label.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { INLINE_TITLE_GROUP_STYLE, @@ -42,6 +43,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoResponsibili export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { const { t } = useTranslation(); const dmsPayers = Form.useWatch(["cdk_configuration", "payers"], form) || []; + const buildSectionActionButton = (key, label, onClick) => ( + + ); + const renderListOrEmpty = (fields, actionLabel, renderItems) => + fields.length === 0 ? : renderItems(); const hasDMSKey = bodyshopHasDmsKey(bodyshop); @@ -513,42 +521,116 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { {!bodyshop.rr_dealerid && ( <> - - - {(fields, { add, remove, move }) => { - return ( + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }) + ]} + >
- {fields.map((field, index) => { - const dmsPayer = dmsPayers[field.name] || {}; + {renderListOrEmpty(fields, t("jobs.actions.addpayer"), () => + fields.map((field, index) => { + const dmsPayer = dmsPayers[field.name] || {}; - return ( + return ( + + + - -
- ); - }} -
-
+
+ ); + }} + )}
@@ -659,142 +671,266 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) { )} {HasFeatureAccess({ featureName: "export", bodyshop }) && ( <> - - - {(fields, { add, remove, move }) => { - return ( + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }) + ]} + >
- {fields.map((field, index) => { - const hasProfitCenterBodyFields = - (hasDMSKey && !bodyshop.rr_dealerid) || bodyshop.cdk_dealerid || bodyshop.rr_dealerid; - - return ( - - - -
-
- {t("bodyshop.fields.responsibilitycenter")} -
- - - -
-
-
-
- {t("bodyshop.fields.responsibilitycenter_accountname")} -
- - - -
-
-
-
- {t("bodyshop.fields.responsibilitycenter_accountdesc")} -
- - - -
- {!hasDMSKey && ( - <> -
-
-
- {t("bodyshop.fields.responsibilitycenter_accountitem")} -
- - - + {renderListOrEmpty(fields, t("bodyshop.actions.add_cost_center"), () => + fields.map((field, index) => { + return ( + + + +
+
+ {t("bodyshop.fields.responsibilitycenter")}
- - )} -
- } - wrapTitle - extra={ - -
+
+
+
+ {t("bodyshop.fields.responsibilitycenter_accountdesc")} +
+ + + +
+
+ } + wrapTitle + extra={ + +
+ + ); + }} + + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }) + ]} + > +
+ {renderListOrEmpty(fields, t("bodyshop.actions.add_profit_center"), () => + fields.map((field, index) => { + return ( + + + +
+
+ {t("bodyshop.fields.responsibilitycenter")} +
+ + + +
+
+
+
+ {t("bodyshop.fields.responsibilitycenter_accountdesc")} +
+ + + +
+
+ } + wrapTitle + extra={ + + -
-
- ); - }} -
- - - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { - const hasProfitCenterBodyFields = - (hasDMSKey && !bodyshop.rr_dealerid) || bodyshop.cdk_dealerid || bodyshop.rr_dealerid; - - return ( - - - -
-
- {t("bodyshop.fields.responsibilitycenter")} -
- - - -
-
-
+ , + -
- {t("bodyshop.fields.responsibilitycenter_accountdesc")} -
- - - -
- {!hasDMSKey && ( - <> -
-
-
- {t("bodyshop.fields.responsibilitycenter_accountitem")} -
- - - -
- - )} - {bodyshop.rr_dealerid && ( - <> -
-
-
- {t("bodyshop.fields.responsibilitycenters.item_type")} -
- - - - )} - {bodyshop.cdk_dealerid && ( - - - - )} - {bodyshop.rr_dealerid && [ - - - , - - + + ]} + + + ); + }) + )}
- ); - }} - - + + ); + }} + {hasDMSKey && ( - - - {(fields, { add, remove }) => { - return ( + + {(fields, { add, remove }) => { + return ( + { + add(); + } + ) + ]} + >
- {fields.map((field, index) => { - const dmsDefault = form.getFieldValue([ - "md_responsibility_centers", - "dms_defaults", - field.name - ]) || { name: undefined }; + {renderListOrEmpty(fields, t("bodyshop.actions.add_dms_allocation"), () => + fields.map((field, index) => { + const dmsDefault = form.getFieldValue([ + "md_responsibility_centers", + "dms_defaults", + field.name + ]) || { name: undefined }; - return ( - - } - onClick={() => { - remove(field.name); - }} - /> - } - > -
- - - - - - - - - - ({ - validator(rule, value) { - if (costOptions.includes(value)) { - return Promise.resolve(); + return ( + + } + onClick={() => { + remove(field.name); + }} + /> + } + > +
+ + + + + + + + + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-ats`} - name={[field.name, "costs", "ATS"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LAA`} - name={[field.name, "costs", "LAA"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LAB`} - name={[field.name, "costs", "LAB"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LAD`} - name={[field.name, "costs", "LAD"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LAE`} - name={[field.name, "costs", "LAE"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LAF`} - name={[field.name, "costs", "LAF"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LAG`} - name={[field.name, "costs", "LAG"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LAM`} - name={[field.name, "costs", "LAM"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LAR`} - name={[field.name, "costs", "LAR"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LAS`} - name={[field.name, "costs", "LAS"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LAU`} - name={[field.name, "costs", "LAU"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LA1`} - name={[field.name, "costs", "LA1"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LA2`} - name={[field.name, "costs", "LA2"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LA3`} - name={[field.name, "costs", "LA3"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-LA4`} - name={[field.name, "costs", "LA4"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PAA`} - name={[field.name, "costs", "PAA"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PAC`} - name={[field.name, "costs", "PAC"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PAG`} - name={[field.name, "costs", "PAG"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PAL`} - name={[field.name, "costs", "PAL"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PAM`} - name={[field.name, "costs", "PAM"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PAN`} - name={[field.name, "costs", "PAN"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PAO`} - name={[field.name, "costs", "PAO"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PAP`} - name={[field.name, "costs", "PAP"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PAR`} - name={[field.name, "costs", "PAR"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PAS`} - name={[field.name, "costs", "PAS"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-PASL`} - name={[field.name, "costs", "PASL"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-TOW`} - name={[field.name, "costs", "TOW"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-MAPA`} - name={[field.name, "costs", "MAPA"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (costOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}costs-MASH`} - name={[field.name, "costs", "MASH"]} - > - ({ value: item, label: item }))} + /> + + + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-ATS`} - name={[field.name, "profits", "ATS"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LAA`} - name={[field.name, "profits", "LAA"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LAB`} - name={[field.name, "profits", "LAB"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LAD`} - name={[field.name, "profits", "LAD"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LAE`} - name={[field.name, "profits", "LAE"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LAF`} - name={[field.name, "profits", "LAF"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LAG`} - name={[field.name, "profits", "LAG"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LAM`} - name={[field.name, "profits", "LAM"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LAR`} - name={[field.name, "profits", "LAR"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LAS`} - name={[field.name, "profits", "LAS"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LAU`} - name={[field.name, "profits", "LAU"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LA1`} - name={[field.name, "profits", "LA1"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LA2`} - name={[field.name, "profits", "LA2"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LA3`} - name={[field.name, "profits", "LA3"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-LA4`} - name={[field.name, "profits", "LA4"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PAA`} - name={[field.name, "profits", "PAA"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PAC`} - name={[field.name, "profits", "PAC"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PAG`} - name={[field.name, "profits", "PAG"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PAL`} - name={[field.name, "profits", "PAL"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PAM`} - name={[field.name, "profits", "PAM"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PAN`} - name={[field.name, "profits", "PAN"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PAO`} - name={[field.name, "profits", "PAO"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PAP`} - name={[field.name, "profits", "PAP"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PAR`} - name={[field.name, "profits", "PAR"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PAS`} - name={[field.name, "profits", "PAS"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-PASL`} - name={[field.name, "profits", "PASL"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-TOW`} - name={[field.name, "profits", "TOW"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-MAPA`} - name={[field.name, "profits", "MAPA"]} - > - ({ value: item, label: item }))} + /> + + ({ + validator(rule, value) { + if (profitOptions.includes(value)) { + return Promise.resolve(); + } + return Promise.reject(t("bodyshop.validation.centermustexist")); } - return Promise.reject(t("bodyshop.validation.centermustexist")); - } - }) - ]} - key={`${index}profits-MASH`} - name={[field.name, "profits", "MASH"]} - > - ({ value: item, label: item }))} + /> + + +
+
+
+ ); + }) + )}
- ); - }} - -
+ + ); + }} + )} @@ -3601,121 +3525,114 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
)} - - - {(fields, { add, remove }) => { - return ( + + {(fields, { add, remove }) => { + return ( + { + add(); + }) + ]} + >
- {fields.map((field, index) => { - return ( - - - -
-
- {t("bodyshop.fields.responsibilitycenters.sales_tax_codes.description")} + {renderListOrEmpty(fields, t("bodyshop.actions.newsalestaxcode"), () => + fields.map((field, index) => { + return ( + + + +
+
+ {t("bodyshop.fields.responsibilitycenters.sales_tax_codes.description")} +
+ + +
- - - -
-
-
-
- {t("bodyshop.fields.responsibilitycenters.sales_tax_codes.code")} +
+
+
+ {t("bodyshop.fields.responsibilitycenters.sales_tax_codes.code")} +
+ + +
- - -
-
- } - wrapTitle - extra={ - - + + + + + + + + + + + + ); + }) + )}
- ); - }} - - + + ); + }} + )} diff --git a/client/src/components/shop-info/shop-info.rostatus.component.jsx b/client/src/components/shop-info/shop-info.rostatus.component.jsx index 273d04bae..05fe27305 100644 --- a/client/src/components/shop-info/shop-info.rostatus.component.jsx +++ b/client/src/components/shop-info/shop-info.rostatus.component.jsx @@ -7,6 +7,7 @@ import { ChromePicker } from "react-color"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; import { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { DEFAULT_TRANSLUCENT_CARD_COLOR, getTintedCardSurfaceStyles } from "./shop-info.color.utils"; @@ -405,106 +406,116 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) { {Production_List_Status_Colors.treatment === "on" && ( - - - {(fields, { add, remove }) => { - return ( + + {(fields, { add, remove }) => { + return ( + { + add({ + color: { ...DEFAULT_TRANSLUCENT_CARD_COLOR } + }); + }} + > + {t("bodyshop.actions.add_production_status_color")} + + ]} + >
- - {fields.map((field, index) => { - const productionColor = productionColors[field.name] || {}; - const productionColorSurfaceStyles = getTintedCardSurfaceStyles(productionColor.color); - const selectedProductionColorStatuses = productionColors - .map((item) => item?.status) - .filter(Boolean); - const productionColorStatusOptions = [ - ...new Set([productionColor.status, ...availableProductionStatuses]) - ] - .filter(Boolean) - .filter( - (status) => - status === productionColor.status || !selectedProductionColorStatuses.includes(status) - ); + {fields.length === 0 ? ( + + ) : ( + + {fields.map((field, index) => { + const productionColor = productionColors[field.name] || {}; + const productionColorSurfaceStyles = getTintedCardSurfaceStyles(productionColor.color); + const selectedProductionColorStatuses = productionColors + .map((item) => item?.status) + .filter(Boolean); + const productionColorStatusOptions = [ + ...new Set([productionColor.status, ...availableProductionStatuses]) + ] + .filter(Boolean) + .filter( + (status) => + status === productionColor.status || !selectedProductionColorStatuses.includes(status) + ); - return ( - - ({ + value: item, + label: item + }))} + /> + + } + extra={ + - + } + {...productionColorSurfaceStyles} + style={{ width: 260, marginBottom: 0 }} + > +
+ + + +
+
+ ); + })} +
+ )}
- ); - }} -
-
+ + ); + }} + )} ); diff --git a/client/src/components/shop-info/shop-info.scheduling.component.jsx b/client/src/components/shop-info/shop-info.scheduling.component.jsx index 12d998d01..8ee367304 100644 --- a/client/src/components/shop-info/shop-info.scheduling.component.jsx +++ b/client/src/components/shop-info/shop-info.scheduling.component.jsx @@ -7,6 +7,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { ColorPicker } from "./shop-info.rostatus.component"; import { @@ -227,195 +228,74 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) { ))} - - - {(fields, { add, remove, move }) => { - return ( + + {(fields, { add, remove, move }) => { + return ( + { + add({ + color: { + ...DEFAULT_TRANSLUCENT_PICKER_COLOR, + rgb: { ...DEFAULT_TRANSLUCENT_PICKER_COLOR.rgb } + } + }); + }} + > + {t("bodyshop.actions.addapptcolor")} + + ]} + >
- {fields.map((field, index) => { - const appointmentColor = - appointmentColors[field.name] || form.getFieldValue(["appt_colors", field.name]) || {}; - const appointmentColorSurfaceStyles = getTintedCardSurfaceStyles(appointmentColor.color); - - return ( - - - - - -
- } - extra={ - - - -
- ); - }} - - - {HasFeatureAccess({ featureName: "smartscheduling", bodyshop }) && ( - - - {(fields, { add, remove, move }) => { - return ( -
- {fields.map((field, index) => { - const schedulingBucket = - schedulingBuckets[field.name] || form.getFieldValue(["ssbuckets", field.name]) || {}; - const schedulingBucketSurfaceStyles = getTintedCardSurfaceStyles(schedulingBucket.color); + {fields.length === 0 ? ( + + ) : ( + fields.map((field, index) => { + const appointmentColor = + appointmentColors[field.name] || form.getFieldValue(["appt_colors", field.name]) || {}; + const appointmentColorSurfaceStyles = getTintedCardSurfaceStyles(appointmentColor.color); return ( -
-
{t("bodyshop.fields.ssbuckets.id")}
- - - -
-
+ -
- {t("bodyshop.fields.ssbuckets.label")} -
- - - -
+ +
} extra={ - + }) + )} +
+
+ ); + }} + + {HasFeatureAccess({ featureName: "smartscheduling", bodyshop }) && ( + + {(fields, { add, remove, move }) => { + return ( + { + add({ + color: { ...DEFAULT_TRANSLUCENT_CARD_COLOR } + }); + }} + > + {t("bodyshop.actions.addbucket")} + + ]} + > +
+ {fields.length === 0 ? ( + + ) : ( + fields.map((field, index) => { + const schedulingBucket = + schedulingBuckets[field.name] || form.getFieldValue(["ssbuckets", field.name]) || {}; + const schedulingBucketSurfaceStyles = getTintedCardSurfaceStyles(schedulingBucket.color); + + return ( + + +
+
{t("bodyshop.fields.ssbuckets.id")}
+ + + +
+
+
+ {t("bodyshop.fields.ssbuckets.label")} +
+ + + +
+
+ } + extra={ + +
- ); - }} -
-
+ + ); + }} + )}
); diff --git a/client/src/components/shop-info/shop-info.speedprint.component.jsx b/client/src/components/shop-info/shop-info.speedprint.component.jsx index 062bea41e..33b5e55fe 100644 --- a/client/src/components/shop-info/shop-info.speedprint.component.jsx +++ b/client/src/components/shop-info/shop-info.speedprint.component.jsx @@ -3,6 +3,7 @@ import { Button, Form, Input, Select, Space } from "antd"; import { useTranslation } from "react-i18next"; import { TemplateList } from "../../utils/TemplateConstants"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { INLINE_TITLE_GROUP_STYLE, @@ -23,133 +24,131 @@ export default function ShopInfoSpeedPrint() { }); return ( - - - {(fields, { add, remove, move }) => { - return ( + + {(fields, { add, remove, move }) => { + return ( + { + add(); + }} + > + {t("bodyshop.actions.addspeedprint")} + + ]} + >
- {fields.map((field, index) => { - return ( - - - -
-
{t("bodyshop.fields.speedprint.id")}
- - - + {fields.length === 0 ? ( + + ) : ( + fields.map((field, index) => { + return ( + + + +
+
{t("bodyshop.fields.speedprint.id")}
+ + + +
+
+
+
{t("bodyshop.fields.speedprint.label")}
+ + + +
-
-
-
{t("bodyshop.fields.speedprint.label")}
- - - -
-
- } - wrapTitle - extra={ - -
- ); - }} - -
+ + ); + }} + ); } diff --git a/client/src/components/shop-info/shop-info.task-presets.component.jsx b/client/src/components/shop-info/shop-info.task-presets.component.jsx index dfc12adfb..d3d3cb66a 100644 --- a/client/src/components/shop-info/shop-info.task-presets.component.jsx +++ b/client/src/components/shop-info/shop-info.task-presets.component.jsx @@ -3,6 +3,7 @@ import { Button, Checkbox, Col, Form, Input, InputNumber, Row, Select, Space, Sw import { useTranslation } from "react-i18next"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import { connect } from "react-redux"; @@ -78,208 +79,216 @@ export function ShopInfoTaskPresets({ bodyshop }) {
- - { - const allocationErrors = getTaskPresetAllocationErrors(presets, t); + { + const allocationErrors = getTaskPresetAllocationErrors(presets, t); - if (allocationErrors.length > 0) { - throw new Error(allocationErrors.join(" ")); - } + if (allocationErrors.length > 0) { + throw new Error(allocationErrors.join(" ")); } } - ]} - > - {(fields, { add, remove, move }, { errors }) => { - return ( + } + ]} + > + {(fields, { add, remove, move }, { errors }) => { + return ( + { + add(); + }} + > + {t("bodyshop.actions.add_task_preset")} + + ]} + >
- {fields.map((field, index) => { - const taskPreset = taskPresets[field.name] || {}; + {fields.length === 0 ? ( + + ) : ( + fields.map((field, index) => { + const taskPreset = taskPresets[field.name] || {}; - return ( - - - - + + {payoutMethod === "commission" ? ( + + ) : ( + + )} + + + ))} + + ); + }} + +
+
+ + ); + }) + )}
- ); - }} -
-
+
+ ); + }} +
)} diff --git a/client/src/components/shop-teams/shop-employee-teams.form.component.test.jsx b/client/src/components/shop-teams/shop-employee-teams.form.component.test.jsx index b7239dbde..da20b74c5 100644 --- a/client/src/components/shop-teams/shop-employee-teams.form.component.test.jsx +++ b/client/src/components/shop-teams/shop-employee-teams.form.component.test.jsx @@ -42,9 +42,11 @@ vi.mock("react-i18next", () => ({ "employee_teams.options.commission": "Commission", "employee_teams.options.commission_percentage": "Commission", "employee_teams.actions.newmember": "New Team Member", + "employee_teams.actions.save_team": "Save Employee Team", "employee_teams.errors.minimum_one_member": "Add at least one team member.", "employee_teams.errors.duplicate_member": "Team members must be unique.", "employee_teams.errors.allocation_total_exact": "Allocation must total exactly 100%.", + "general.labels.click_to_begin": `Click ${values.action ?? ""} to begin`, "general.actions.save": "Save", "employees.successes.save": "Saved" }; @@ -101,11 +103,12 @@ vi.mock("../form-items-formatted/currency-form-item.component", () => ({ })); vi.mock("../layout-form-row/layout-form-row.component", () => ({ - default: ({ title, extra, children }) => ( + default: ({ title, extra, actions, children }) => (
{title} {extra} {children} + {actions}
) })); @@ -211,7 +214,7 @@ describe("ShopEmployeeTeamsFormComponent", () => { rate: 27.5 }); - fireEvent.click(screen.getByRole("button", { name: "Save" })); + fireEvent.click(screen.getByRole("button", { name: "Save Employee Team" })); await waitFor(() => { expect(insertEmployeeTeamMock).toHaveBeenCalledWith({ diff --git a/client/src/components/shop-teams/shop-employee-teams.list.jsx b/client/src/components/shop-teams/shop-employee-teams.list.jsx index 4c0ac837b..8cd351201 100644 --- a/client/src/components/shop-teams/shop-employee-teams.list.jsx +++ b/client/src/components/shop-teams/shop-employee-teams.list.jsx @@ -2,6 +2,8 @@ import { Button } from "antd"; import queryString from "query-string"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate } from "react-router-dom"; +import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx"; +import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import ResponsiveTable from "../responsive-table/responsive-table.component"; export default function ShopEmployeeTeamsListComponent({ loading, employee_teams }) { @@ -9,13 +11,28 @@ export default function ShopEmployeeTeamsListComponent({ loading, employee_teams const history = useNavigate(); const search = queryString.parse(useLocation().search); + const navigateToTeam = (employeeTeamId) => { + history({ + search: queryString.stringify({ + ...search, + employeeTeamId + }) + }); + }; + + const clearTeamSelection = () => { + const { employeeTeamId, ...nextSearch } = search; + void employeeTeamId; + history({ + search: queryString.stringify(nextSearch) + }); + }; + const handleOnRowClick = (record) => { if (record) { - search.employeeTeamId = record.id; - history({ search: queryString.stringify(search) }); + navigateToTeam(record.id); } else { - delete search.employeeTeamId; - history({ search: queryString.stringify(search) }); + clearTeamSelection(); } }; const columns = [ @@ -27,43 +44,38 @@ export default function ShopEmployeeTeamsListComponent({ loading, employee_teams ]; return ( -
- { - return ( - - ); - }} - loading={loading} - pagination={{ placement: "top" }} - columns={columns} - mobileColumnKeys={["name"]} - rowKey="id" - dataSource={employee_teams} - rowSelection={{ - onSelect: (props) => { - search.employeeTeamId = props.id; - history({ search: queryString.stringify(search) }); - }, - type: "radio", - selectedRowKeys: [search.employeeTeamId] - }} - onRow={(record) => { - return { - onClick: () => { - handleOnRowClick(record); - } - }; - }} - /> -
+ navigateToTeam("new")}> + {t("employee_teams.actions.new")} + + ]} + > + {employee_teams.length === 0 ? ( + + ) : ( + navigateToTeam(props.id), + type: "radio", + selectedRowKeys: [search.employeeTeamId] + }} + onRow={(record) => { + return { + onClick: () => { + handleOnRowClick(record); + } + }; + }} + /> + )} + ); } diff --git a/client/src/components/shop-users/shop-users.component.jsx b/client/src/components/shop-users/shop-users.component.jsx index d3fd16b45..d4e972b11 100644 --- a/client/src/components/shop-users/shop-users.component.jsx +++ b/client/src/components/shop-users/shop-users.component.jsx @@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect"; import { QUERY_SHOP_ASSOCIATIONS } from "../../graphql/user.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; +import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import ResponsiveTable from "../responsive-table/responsive-table.component"; import ShopUsersAuthEdit from "../shop-users-auth-edit/shop-users-auth-edit.component"; @@ -66,7 +67,7 @@ export function ShopInfoUsersComponent({ bodyshop }) { return ; } return ( -
+ -
+
); } diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index d2584bea0..428894541 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -292,7 +292,23 @@ }, "bodyshop": { "actions": { + "add_adjuster": "Add Adjuster", + "add_control_number": "Add Control Number", + "add_cost_center": "Add Cost Center", + "add_courtesy_car_rate_preset": "Add Courtesy Car Contract Rate Preset", + "add_delivery_checklist_item": "Add Delivery Checklist Item", + "add_dms_allocation": "Add DMS Allocation", + "add_estimator": "Add Estimator", + "add_insurance_company": "Add Insurance Company", + "add_intake_checklist_item": "Add Intake Checklist Item", + "add_jobline_preset": "Add Jobline Preset", + "add_messaging_preset": "Add Messaging Preset", + "add_note_preset": "Add Note Preset", + "add_parts_order_comment": "Add Parts Order Comment", + "add_production_status_color": "Add Production Status Color", + "add_profit_center": "Add Profit Center", "add_task_preset": "Add Task Preset", + "add_to_email_preset": "Add To Email Preset", "addapptcolor": "Add Appointment Color", "addbucket": "Add Definition", "addpartslocation": "Add Parts Location", @@ -301,6 +317,7 @@ "addtemplate": "Add Template", "newlaborrate": "New Labor Rate", "newsalestaxcode": "New Sales Tax Code", + "save_shop_information": "Save Shop Information", "newstatus": "Add Status", "testrender": "Test Render" }, @@ -1206,7 +1223,8 @@ "employee_teams": { "actions": { "new": "New Team", - "newmember": "New Team Member" + "newmember": "New Team Member", + "save_team": "Save Employee Team" }, "errors": { "allocation_total_exact": "Team allocation must total exactly 100%.", @@ -1236,9 +1254,11 @@ }, "employees": { "actions": { + "addrate": "Add Rate", "addvacation": "Add Vacation", "new": "New Employee", "newrate": "New Rate", + "save_employee": "Save Employee", "select": "Select Employee" }, "errors": { @@ -1403,6 +1423,7 @@ "beta": "BETA", "cancel": "Are you sure you want to cancel? Your changes will not be saved.", "changelog": "Change Log", + "click_to_begin": "Click {{action}} to begin", "clear": "Clear", "confirmpassword": "Confirm Password", "created_at": "Created At", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 0fd4e9622..4a12af3c8 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -292,7 +292,23 @@ }, "bodyshop": { "actions": { + "add_adjuster": "", + "add_control_number": "", + "add_cost_center": "", + "add_courtesy_car_rate_preset": "", + "add_delivery_checklist_item": "", + "add_dms_allocation": "", + "add_estimator": "", + "add_insurance_company": "", + "add_intake_checklist_item": "", + "add_jobline_preset": "", + "add_messaging_preset": "", + "add_note_preset": "", + "add_parts_order_comment": "", + "add_production_status_color": "", + "add_profit_center": "", "add_task_preset": "", + "add_to_email_preset": "", "addapptcolor": "", "addbucket": "", "addpartslocation": "", @@ -301,6 +317,7 @@ "addtemplate": "", "newlaborrate": "", "newsalestaxcode": "", + "save_shop_information": "", "newstatus": "", "testrender": "" }, @@ -1206,7 +1223,8 @@ "employee_teams": { "actions": { "new": "", - "newmember": "" + "newmember": "", + "save_team": "" }, "errors": { "allocation_total_exact": "", @@ -1236,9 +1254,11 @@ }, "employees": { "actions": { + "addrate": "", "addvacation": "", "new": "Nuevo empleado", "newrate": "", + "save_employee": "", "select": "" }, "errors": { @@ -1403,6 +1423,7 @@ "beta": "", "cancel": "", "changelog": "", + "click_to_begin": "", "clear": "", "confirmpassword": "", "created_at": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 11b4fdc7f..b0391e190 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -292,7 +292,23 @@ }, "bodyshop": { "actions": { + "add_adjuster": "", + "add_control_number": "", + "add_cost_center": "", + "add_courtesy_car_rate_preset": "", + "add_delivery_checklist_item": "", + "add_dms_allocation": "", + "add_estimator": "", + "add_insurance_company": "", + "add_intake_checklist_item": "", + "add_jobline_preset": "", + "add_messaging_preset": "", + "add_note_preset": "", + "add_parts_order_comment": "", + "add_production_status_color": "", + "add_profit_center": "", "add_task_preset": "", + "add_to_email_preset": "", "addapptcolor": "", "addbucket": "", "addpartslocation": "", @@ -301,6 +317,7 @@ "addtemplate": "", "newlaborrate": "", "newsalestaxcode": "", + "save_shop_information": "", "newstatus": "", "testrender": "" }, @@ -1206,7 +1223,8 @@ "employee_teams": { "actions": { "new": "", - "newmember": "" + "newmember": "", + "save_team": "" }, "errors": { "allocation_total_exact": "", @@ -1236,9 +1254,11 @@ }, "employees": { "actions": { + "addrate": "", "addvacation": "", "new": "Nouvel employé", "newrate": "", + "save_employee": "", "select": "" }, "errors": { @@ -1403,6 +1423,7 @@ "beta": "", "cancel": "", "changelog": "", + "click_to_begin": "", "clear": "", "confirmpassword": "", "created_at": "",