IO-3624 Polish remaining shop config section cards

This commit is contained in:
Dave
2026-03-24 11:51:55 -04:00
parent d23a182650
commit c33a3118bc
9 changed files with 302 additions and 287 deletions

View File

@@ -22,6 +22,66 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
const deliverChecklistItems = Form.useWatch(["deliverchecklist", "form"], form) || []; const deliverChecklistItems = Form.useWatch(["deliverchecklist", "form"], form) || [];
return ( return (
<div> <div>
<SelectorDiv>
<LayoutFormRow header={t("bodyshop.labels.intake_delivery")} id="intake-delivery">
<Form.Item
name={["intakechecklist", "templates"]}
label={t("bodyshop.fields.intake.templates")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select
mode="multiple"
options={Object.keys(TemplateListGenerated).map((i) => ({
value: TemplateListGenerated[i].key,
label: TemplateListGenerated[i].title
}))}
/>
</Form.Item>
<Form.Item
name={["intakechecklist", "next_contact_hours"]}
label={t("bodyshop.fields.intake.next_contact_hours")}
>
<InputNumber min={0} precision={0} />
</Form.Item>
<Form.Item
name={["deliverchecklist", "templates"]}
label={t("bodyshop.fields.deliver.templates")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select
mode="multiple"
options={Object.keys(TemplateListGenerated).map((i) => ({
value: TemplateListGenerated[i].key,
label: TemplateListGenerated[i].title
}))}
/>
</Form.Item>
<Form.Item
name={["deliverchecklist", "actual_delivery"]}
label={t("bodyshop.fields.deliver.require_actual_delivery_date")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Switch />
</Form.Item>
</LayoutFormRow>
</SelectorDiv>
<LayoutFormRow header={t("bodyshop.labels.intakechecklist")} id="intakechecklist"> <LayoutFormRow header={t("bodyshop.labels.intakechecklist")} id="intakechecklist">
<Form.List name={["intakechecklist", "form"]}> <Form.List name={["intakechecklist", "form"]}>
{(fields, { add, remove, move }) => { {(fields, { add, remove, move }) => {
@@ -163,34 +223,6 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
}} }}
</Form.List> </Form.List>
</LayoutFormRow> </LayoutFormRow>
<SelectorDiv>
<Form.Item
name={["intakechecklist", "templates"]}
label={t("bodyshop.fields.intake.templates")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select
mode="multiple"
options={Object.keys(TemplateListGenerated).map((i) => ({
value: TemplateListGenerated[i].key,
label: TemplateListGenerated[i].title
}))}
/>
</Form.Item>
<Form.Item
name={["intakechecklist", "next_contact_hours"]}
label={t("bodyshop.fields.intake.next_contact_hours")}
>
<InputNumber min={0} precision={0} />
</Form.Item>
</SelectorDiv>
<LayoutFormRow header={t("bodyshop.labels.deliverchecklist")} id="deliverchecklist"> <LayoutFormRow header={t("bodyshop.labels.deliverchecklist")} id="deliverchecklist">
<Form.List name={["deliverchecklist", "form"]}> <Form.List name={["deliverchecklist", "form"]}>
{(fields, { add, remove, move }) => { {(fields, { add, remove, move }) => {
@@ -335,39 +367,6 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
}} }}
</Form.List> </Form.List>
</LayoutFormRow> </LayoutFormRow>
<SelectorDiv>
<Form.Item
name={["deliverchecklist", "templates"]}
label={t("bodyshop.fields.deliver.templates")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select
mode="multiple"
options={Object.keys(TemplateListGenerated).map((i) => ({
value: TemplateListGenerated[i].key,
label: TemplateListGenerated[i].title
}))}
/>
</Form.Item>
<Form.Item
name={["deliverchecklist", "actual_delivery"]}
label={t("bodyshop.fields.deliver.require_actual_delivery_date")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Switch />
</Form.Item>
</SelectorDiv>
</div> </div>
); );
} }

View File

@@ -1,6 +1,7 @@
import { Form, Typography } from "antd"; import { Form, Typography } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component.jsx"; import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component.jsx";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const { Text, Paragraph } = Typography; const { Text, Paragraph } = Typography;
@@ -11,43 +12,45 @@ export default function ShopInfoNotificationsAutoadd({ bodyshop }) {
const employeeOptions = bodyshop?.employees?.filter((e) => e.active && e.user_email && e.id) || []; const employeeOptions = bodyshop?.employees?.filter((e) => e.active && e.user_email && e.id) || [];
return ( return (
<div> <LayoutFormRow header={t("bodyshop.labels.notification_options")}>
<Paragraph>{t("bodyshop.fields.notifications.description")}</Paragraph> <div>
<Text type="secondary">{t("bodyshop.labels.notifications.followers")}</Text> <Paragraph>{t("bodyshop.fields.notifications.description")}</Paragraph>
{employeeOptions.length > 0 ? ( <Text type="secondary">{t("bodyshop.labels.notifications.followers")}</Text>
<Form.Item {employeeOptions.length > 0 ? (
normalize={(value) => (value || []).filter((id) => typeof id === "string" && id.trim() !== "")} <Form.Item
name="notification_followers" normalize={(value) => (value || []).filter((id) => typeof id === "string" && id.trim() !== "")}
rules={[ name="notification_followers"
{ rules={[
type: "array", {
message: t("general.validation.array") type: "array",
}, message: t("general.validation.array")
{ },
validator: async (_, value) => { {
if (!value || value.length === 0) { validator: async (_, value) => {
return Promise.resolve(); // Allow empty array if (!value || value.length === 0) {
return Promise.resolve(); // Allow empty array
}
const hasInvalid = value.some((id) => id == null || typeof id !== "string" || id.trim() === "");
if (hasInvalid) {
return Promise.reject(new Error(t("bodyshop.fields.notifications.invalid_followers")));
}
return Promise.resolve();
} }
const hasInvalid = value.some((id) => id == null || typeof id !== "string" || id.trim() === "");
if (hasInvalid) {
return Promise.reject(new Error(t("bodyshop.fields.notifications.invalid_followers")));
}
return Promise.resolve();
} }
} ]}
]} >
> <EmployeeSearchSelectComponent
<EmployeeSearchSelectComponent style={{ minWidth: "100%" }}
style={{ minWidth: "100%" }} mode="multiple"
mode="multiple" options={employeeOptions}
options={employeeOptions} placeholder={t("bodyshop.fields.notifications.placeholder")}
placeholder={t("bodyshop.fields.notifications.placeholder")} showEmail={true}
showEmail={true} />
/> </Form.Item>
</Form.Item> ) : (
) : ( <Text type="secondary">{t("bodyshop.fields.no_employees_available")}</Text>
<Text type="secondary">{t("bodyshop.fields.no_employees_available")}</Text> )}
)} </div>
</div> </LayoutFormRow>
); );
} }

View File

@@ -27,7 +27,7 @@ export function ShopInfoRbacComponent({ bodyshop }) {
}); });
return ( return (
<RbacWrapper action="shop:rbac"> <RbacWrapper action="shop:rbac">
<LayoutFormRow> <LayoutFormRow header={t("bodyshop.labels.rbac_options")}>
{[ {[
...(HasFeatureAccess({ featureName: "export", bodyshop }) ...(HasFeatureAccess({ featureName: "export", bodyshop })
? [ ? [

View File

@@ -21,7 +21,7 @@ export default function ShopInfoRoGuard({ form }) {
{() => { {() => {
const disabled = !form.getFieldValue(["md_ro_guard", "enabled"]); const disabled = !form.getFieldValue(["md_ro_guard", "enabled"]);
return ( return (
<LayoutFormRow noDivider> <LayoutFormRow header={t("bodyshop.labels.md_ro_guard_options")}>
<Form.Item <Form.Item
label={t("bodyshop.fields.md_ro_guard.totalgppercent_minimum")} label={t("bodyshop.fields.md_ro_guard.totalgppercent_minimum")}
name={["md_ro_guard", "totalgppercent_minimum"]} name={["md_ro_guard", "totalgppercent_minimum"]}

View File

@@ -45,98 +45,102 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
return ( return (
<SelectorDiv id="jobstatus"> <SelectorDiv id="jobstatus">
<Form.Item <LayoutFormRow grow header={t("bodyshop.labels.job_status_options")}>
name={["md_ro_statuses", "statuses"]} <div>
label={t("bodyshop.labels.alljobstatuses")} <Form.Item
rules={[ name={["md_ro_statuses", "statuses"]}
{ label={t("bodyshop.labels.alljobstatuses")}
required: true, rules={[
//message: t("general.validation.required"), {
type: "array" required: true,
} //message: t("general.validation.required"),
]} type: "array"
> }
<Select mode="tags" /> ]}
</Form.Item> >
<Form.Item <Select mode="tags" />
name={["md_ro_statuses", "active_statuses"]} </Form.Item>
label={t("bodyshop.fields.statuses.active_statuses")} <Form.Item
rules={[ name={["md_ro_statuses", "active_statuses"]}
{ label={t("bodyshop.fields.statuses.active_statuses")}
required: true, rules={[
//message: t("general.validation.required"), {
type: "array" required: true,
} //message: t("general.validation.required"),
]} type: "array"
> }
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} /> ]}
</Form.Item> >
<Form.Item <Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
name={["md_ro_statuses", "pre_production_statuses"]} </Form.Item>
label={t("bodyshop.fields.statuses.pre_production_statuses")} <Form.Item
rules={[ name={["md_ro_statuses", "pre_production_statuses"]}
{ label={t("bodyshop.fields.statuses.pre_production_statuses")}
required: true, rules={[
//message: t("general.validation.required"), {
type: "array" required: true,
} //message: t("general.validation.required"),
]} type: "array"
> }
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} /> ]}
</Form.Item> >
<Form.Item <Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
name={["md_ro_statuses", "production_statuses"]} </Form.Item>
label={t("bodyshop.fields.statuses.production_statuses")} <Form.Item
rules={[ name={["md_ro_statuses", "production_statuses"]}
{ label={t("bodyshop.fields.statuses.production_statuses")}
required: true, rules={[
//message: t("general.validation.required"), {
type: "array" required: true,
} //message: t("general.validation.required"),
]} type: "array"
> }
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} /> ]}
</Form.Item> >
<Form.Item <Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
name={["md_ro_statuses", "post_production_statuses"]} </Form.Item>
label={t("bodyshop.fields.statuses.post_production_statuses")} <Form.Item
rules={[ name={["md_ro_statuses", "post_production_statuses"]}
{ label={t("bodyshop.fields.statuses.post_production_statuses")}
required: true, rules={[
//message: t("general.validation.required"), {
type: "array" required: true,
} //message: t("general.validation.required"),
]} type: "array"
> }
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} /> ]}
</Form.Item> >
<Form.Item <Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
name={["md_ro_statuses", "ready_statuses"]} </Form.Item>
label={t("bodyshop.fields.statuses.ready_statuses")} <Form.Item
rules={[ name={["md_ro_statuses", "ready_statuses"]}
{ label={t("bodyshop.fields.statuses.ready_statuses")}
//required: true, rules={[
//message: t("general.validation.required"), {
type: "array" //required: true,
} //message: t("general.validation.required"),
]} type: "array"
> }
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} /> ]}
</Form.Item> >
<Form.Item <Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
name={["md_ro_statuses", "additional_board_statuses"]} </Form.Item>
label={t("bodyshop.fields.statuses.additional_board_statuses")} <Form.Item
rules={[ name={["md_ro_statuses", "additional_board_statuses"]}
{ label={t("bodyshop.fields.statuses.additional_board_statuses")}
//required: true, rules={[
//message: t("general.validation.required"), {
type: "array" //required: true,
} //message: t("general.validation.required"),
]} type: "array"
> }
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} /> ]}
</Form.Item> >
<LayoutFormRow noDivider> <Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
</div>
</LayoutFormRow>
<LayoutFormRow grow header={t("general.actions.defaults")}>
<Form.Item <Form.Item
label={t("bodyshop.fields.statuses.default_scheduled")} label={t("bodyshop.fields.statuses.default_scheduled")}
rules={[ rules={[

View File

@@ -18,100 +18,102 @@ export default function ShopInfoSpeedPrint() {
}); });
return ( return (
<Form.List name={["speedprint"]}> <LayoutFormRow header={t("bodyshop.labels.speedprint_configurations")}>
{(fields, { add, remove, move }) => { <Form.List name={["speedprint"]}>
return ( {(fields, { add, remove, move }) => {
<div> return (
{fields.map((field, index) => { <div>
const speedPrintItem = speedPrintItems[field.name] || {}; {fields.map((field, index) => {
const speedPrintItem = speedPrintItems[field.name] || {};
return ( return (
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}> <Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
<LayoutFormRow <LayoutFormRow
grow grow
title={getFormListItemTitle( title={getFormListItemTitle(
t("bodyshop.fields.speedprint.label"), t("bodyshop.fields.speedprint.label"),
index, index,
speedPrintItem.label, speedPrintItem.label,
speedPrintItem.id speedPrintItem.id
)} )}
extra={ extra={
<Space align="center" size="small"> <Space align="center" size="small">
<Button <Button
type="text" type="text"
icon={<DeleteFilled />} icon={<DeleteFilled />}
onClick={() => { onClick={() => {
remove(field.name); remove(field.name);
}} }}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} orientation="horizontal" />
</Space>
}
>
<Form.Item
label={t("bodyshop.fields.speedprint.id")}
key={`${index}id`}
name={[field.name, "id"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.speedprint.label")}
key={`${index}label`}
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
name={[field.name, "templates"]}
label={t("bodyshop.fields.speedprint.templates")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select
mode="multiple"
options={Object.keys(TemplateListGenerated).map((key) => ({
value: TemplateListGenerated[key].key,
label: TemplateListGenerated[key].title
}))}
/> />
<FormListMoveArrows move={move} index={index} total={fields.length} orientation="horizontal" /> </Form.Item>
</Space> </LayoutFormRow>
} </Form.Item>
> );
<Form.Item })}
label={t("bodyshop.fields.speedprint.id")} <Form.Item>
key={`${index}id`} <Button
name={[field.name, "id"]} type="dashed"
rules={[ onClick={() => {
{ add();
required: true }}
//message: t("general.validation.required"), style={{ width: "100%" }}
} >
]} {t("bodyshop.actions.addspeedprint")}
> </Button>
<Input /> </Form.Item>
</Form.Item> </div>
<Form.Item );
label={t("bodyshop.fields.speedprint.label")} }}
key={`${index}label`} </Form.List>
name={[field.name, "label"]} </LayoutFormRow>
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
name={[field.name, "templates"]}
label={t("bodyshop.fields.speedprint.templates")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select
mode="multiple"
options={Object.keys(TemplateListGenerated).map((key) => ({
value: TemplateListGenerated[key].key,
label: TemplateListGenerated[key].title
}))}
/>
</Form.Item>
</LayoutFormRow>
</Form.Item>
);
})}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("bodyshop.actions.addspeedprint")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
); );
} }

View File

@@ -61,7 +61,7 @@ export function ShopInfoTaskPresets({ bodyshop }) {
return ( return (
<> <>
<LayoutFormRow noDivider> <LayoutFormRow header={t("bodyshop.labels.task_preset_options")}>
<Form.Item <Form.Item
label={t("bodyshop.fields.md_tasks_presets.enable_tasks")} label={t("bodyshop.fields.md_tasks_presets.enable_tasks")}
valuePropName="checked" valuePropName="checked"

View File

@@ -20,7 +20,7 @@ export function ShopInfoIntellipay({ bodyshop, form }) {
return ( return (
<> <>
<Form.Item dependencies={[["intellipay_config", "enable_cash_discount"]]}> <Form.Item dependencies={[["intellipay_config", "enable_cash_discount"]]} style={{ marginBottom: 20 }}>
{() => { {() => {
const { intellipay_config } = form.getFieldsValue(); const { intellipay_config } = form.getFieldsValue();
@@ -29,7 +29,7 @@ export function ShopInfoIntellipay({ bodyshop, form }) {
}} }}
</Form.Item> </Form.Item>
<LayoutFormRow noDivider> <LayoutFormRow header={t("bodyshop.labels.imexpay")}>
<Form.Item <Form.Item
label={t("bodyshop.fields.intellipay_config.enable_cash_discount")} label={t("bodyshop.fields.intellipay_config.enable_cash_discount")}
valuePropName="checked" valuePropName="checked"

View File

@@ -741,17 +741,22 @@
"filehandlers": "Adjusters", "filehandlers": "Adjusters",
"imexpay": "ImEX Pay", "imexpay": "ImEX Pay",
"insurancecos": "Insurance Companies", "insurancecos": "Insurance Companies",
"intake_delivery": "Intake / Delivery Options",
"intakechecklist": "Intake Checklist", "intakechecklist": "Intake Checklist",
"intellipay_cash_discount": "Please ensure that cash discounting has been enabled on your merchant account. Reach out to IntelliPay Support if you need assistance. ", "intellipay_cash_discount": "Please ensure that cash discounting has been enabled on your merchant account. Reach out to IntelliPay Support if you need assistance. ",
"job_status_options": "Job Status Options",
"jobstatuses": "Job Statuses", "jobstatuses": "Job Statuses",
"laborrates": "Labor Rates", "laborrates": "Labor Rates",
"licensing": "Licensing", "licensing": "Licensing",
"md_parts_scan": "Parts Scan Rules", "md_parts_scan": "Parts Scan Rules",
"md_ro_guard": "RO Guard", "md_ro_guard": "RO Guard",
"md_ro_guard_options": "RO Guard Options",
"md_tasks_presets": "Tasks Presets", "md_tasks_presets": "Tasks Presets",
"task_preset_options": "Task Preset Options",
"md_to_emails": "Preset To Emails", "md_to_emails": "Preset To Emails",
"md_to_emails_emails": "Emails", "md_to_emails_emails": "Emails",
"messagingpresets": "Messaging Presets", "messagingpresets": "Messaging Presets",
"notification_options": "Notification Options",
"notemplatesavailable": "No templates available to add.", "notemplatesavailable": "No templates available to add.",
"notespresets": "Notes Presets", "notespresets": "Notes Presets",
"notifications": { "notifications": {
@@ -767,6 +772,7 @@
"qbo_departmentid": "QBO Department ID", "qbo_departmentid": "QBO Department ID",
"qbo_usa": "QBO USA Compatibility", "qbo_usa": "QBO USA Compatibility",
"rbac": "Role Based Access Control", "rbac": "Role Based Access Control",
"rbac_options": "Role Based Access Control Options",
"responsibilitycenters": { "responsibilitycenters": {
"costs": "Cost Centers", "costs": "Cost Centers",
"profits": "Profit Centers", "profits": "Profit Centers",
@@ -786,6 +792,7 @@
"shopinfo": "Shop Information", "shopinfo": "Shop Information",
"shoprates": "Shop Rates", "shoprates": "Shop Rates",
"speedprint": "Speed Print Configuration", "speedprint": "Speed Print Configuration",
"speedprint_configurations": "Speed Print Configurations",
"ssbuckets": "Job Size Definitions", "ssbuckets": "Job Size Definitions",
"systemsettings": "System Settings", "systemsettings": "System Settings",
"task-presets": "Task Presets", "task-presets": "Task Presets",