Files
bodyshop/client/src/components/shop-info/shop-info.general.component.jsx
Dave Richer 9267e584ff Merged in hotfix/2026-04-21 (pull request #3201)
hotfix/2026-04-21 - fix Parts order comments
2026-04-22 16:43:19 +00:00

1807 lines
76 KiB
JavaScript

import { DeleteFilled } from "@ant-design/icons";
import { Button, Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import FeatureWrapper from "../feature-wrapper/feature-wrapper.component";
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 FormItemUrl from "../form-items-formatted/url-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import { buildSectionActionButton, renderListOrEmpty } from "../layout-form-row/config-list-actions.utils.jsx";
import InlineValidatedFormRow from "../layout-form-row/inline-validated-form-row.component.jsx";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { bodyshopHasDmsKey, DMS_MAP, getDmsMode } from "../../utils/dmsUtils.js";
import {
INLINE_TITLE_GROUP_STYLE,
INLINE_TITLE_HANDLE_STYLE,
INLINE_TITLE_INPUT_STYLE,
INLINE_TITLE_LABEL_STYLE,
INLINE_TITLE_ROW_STYLE,
INLINE_TITLE_SEPARATOR_STYLE,
INLINE_TITLE_SWITCH_GROUP_STYLE,
InlineTitleListIcon
} from "../layout-form-row/inline-form-row-title.utils.js";
const timeZonesList = Intl.supportedValuesOf("timeZone");
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral);
export function ShopInfoGeneral({ form, bodyshop }) {
const { t } = useTranslation();
const insuranceCompanies = Form.useWatch(["md_ins_cos"], form) || [];
const duplicateInsuranceCompanyIndexes = getDuplicateIndexSetByNormalizedName(insuranceCompanies, "name");
const hasDMSKey = bodyshop ? bodyshopHasDmsKey(bodyshop) : false;
const dmsMode = bodyshop ? getDmsMode(bodyshop, "off") : "none";
const isReynoldsMode = hasDMSKey && dmsMode === DMS_MAP.reynolds;
return (
<div>
<LayoutFormRow header={t("bodyshop.labels.businessinformation")} id="businessinformation">
<Form.Item
label={t("bodyshop.fields.shopname")}
name="shopname"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.address1")}
name="address1"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item label={t("bodyshop.fields.address2")} name="address2">
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.city")}
name="city"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.state")}
name="state"
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item label={t("bodyshop.fields.zip_post")} name="zip_post">
<Input />
</Form.Item>
<Form.Item label={t("bodyshop.fields.country")} name="country">
<Input />
</Form.Item>
<Form.Item label={t("bodyshop.fields.email")} name="email">
<FormItemEmail />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.phone")}
name="phone"
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "phone")]}
>
<PhoneFormItem formatDisplayOnly showPhoneAction />
</Form.Item>
<Form.Item label={t("bodyshop.fields.website")} name="website">
<FormItemUrl />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.timezone")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name="timezone"
>
<Select
showSearch
options={timeZonesList.map((z) => {
return { label: z, value: z };
})}
/>
</Form.Item>
<Form.Item label={t("bodyshop.fields.insurance_vendor_id")} name="insurance_vendor_id">
<Input />
</Form.Item>
<Form.Item label={t("bodyshop.fields.logo_img_path")} name={["logo_img_path", "src"]}>
<FormItemUrl />
</Form.Item>
<Form.Item label={t("bodyshop.fields.logo_img_path_height")} name={["logo_img_path", "height"]}>
<Input suffix="px" />
</Form.Item>
<Form.Item label={t("bodyshop.fields.logo_img_path_width")} name={["logo_img_path", "width"]}>
<Input suffix="px" />
</Form.Item>
<Form.Item label={t("bodyshop.fields.logo_img_header_margin")} name={["logo_img_path", "headerMargin"]}>
<InputNumber min={0} suffix="px" />
</Form.Item>
<Form.Item label={t("bodyshop.fields.logo_img_footer_margin")} name={["logo_img_path", "footerMargin"]}>
<InputNumber min={0} suffix="px" />
</Form.Item>
</LayoutFormRow>
<FeatureWrapper featureName="scoreboard" noauth={() => null}>
<LayoutFormRow
id="scoreboardsetup"
title={
<div
style={{
...INLINE_TITLE_ROW_STYLE,
justifyContent: "space-between"
}}
>
<div
style={{
fontWeight: 500,
marginRight: "auto"
}}
>
{t("bodyshop.labels.scoreboardsetup")}
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: 4,
flexWrap: "wrap",
marginLeft: "auto"
}}
>
<div aria-hidden style={INLINE_TITLE_SEPARATOR_STYLE} />
<div style={INLINE_TITLE_SWITCH_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>
{t("bodyshop.fields.scoreboard_setup.ignore_blocked_days")}
</div>
<Form.Item noStyle name={["scoreboard_target", "ignoreblockeddays"]} valuePropName="checked">
<Switch />
</Form.Item>
</div>
</div>
</div>
}
wrapTitle
>
<Form.Item
label={t("bodyshop.fields.scoreboard_setup.daily_paint_target")}
name={["scoreboard_target", "dailyPaintTarget"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.scoreboard_setup.daily_body_target")}
name={["scoreboard_target", "dailyBodyTarget"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.scoreboard_setup.last_number_working_days")}
name={["scoreboard_target", "lastNumberWorkingDays"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} max={12} precision={0} suffix="days" />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.scoreboard_setup.production_target_hours")}
name={["prodtargethrs"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={1} precision={1} suffix="hrs" />
</Form.Item>
</LayoutFormRow>
</FeatureWrapper>
<LayoutFormRow header={t("bodyshop.labels.systemsettings")} id="systemsettings">
<>
<Form.Item
key="md_categories"
name={["md_categories"]}
label={t("bodyshop.fields.md_categories")}
rules={[
{
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item
key="md_referral_sources"
name={["md_referral_sources"]}
label={t("bodyshop.fields.md_referral_sources")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item
key="md_ded_notes"
name={["md_ded_notes"]}
label={t("bodyshop.fields.md_ded_notes")}
rules={[
{
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
<Row gutter={[16, 0]} wrap>
<Col xs={24} sm={12} xl={8}>
<Form.Item
key="enforce_referral"
name={["enforce_referral"]}
label={t("bodyshop.fields.enforce_referral")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</Col>
<Col xs={24} sm={12} xl={8}>
<Form.Item
key="enforce_conversion_csr"
name={["enforce_conversion_csr"]}
label={t("bodyshop.fields.enforce_conversion_csr")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</Col>
<Col xs={24} sm={12} xl={8}>
<Form.Item
key="enforce_conversion_category"
name={["enforce_conversion_category"]}
label={t("bodyshop.fields.enforce_conversion_category")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</Col>
<Col xs={24} sm={12} xl={8}>
<Form.Item
key="use_fippa"
label={t("bodyshop.fields.use_fippa")}
name={["use_fippa"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</Col>
<Col xs={24} sm={12} xl={8}>
<Form.Item
key="parts_queue_toggle"
label={t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
name={["md_functionality_toggles", "parts_queue_toggle"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</Col>
<Col xs={24} sm={12} xl={8}>
<Form.Item
key="last_name_first"
name={["last_name_first"]}
label={t("bodyshop.fields.last_name_first")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</Col>
</Row>
<div style={{ display: "grid", gap: 16, marginTop: 16 }}>
<LayoutFormRow
grow
style={{ marginBottom: 0 }}
title={
<div
style={{
...INLINE_TITLE_ROW_STYLE,
justifyContent: "space-between"
}}
>
<div
style={{
fontWeight: 500,
marginRight: "auto"
}}
>
{t("bodyshop.labels.localmediaserver")}
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: 4,
flexWrap: "wrap",
marginLeft: "auto"
}}
>
<div aria-hidden style={INLINE_TITLE_SEPARATOR_STYLE} />
<div style={INLINE_TITLE_SWITCH_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>
{t("bodyshop.fields.system_settings.local_media_server.enabled")}
</div>
<Form.Item noStyle name={["uselocalmediaserver"]} valuePropName="checked">
<Switch />
</Form.Item>
</div>
</div>
</div>
}
wrapTitle
>
<Form.Item
key="localmediaserverhttp"
name={["localmediaserverhttp"]}
label={t("bodyshop.fields.system_settings.local_media_server.http_path")}
>
<FormItemUrl />
</Form.Item>
<Form.Item
key="localmediaservernetwork"
name={["localmediaservernetwork"]}
label={t("bodyshop.fields.system_settings.local_media_server.network_path")}
>
<Input />
</Form.Item>
<Form.Item
key="localmediatoken"
name={["localmediatoken"]}
label={t("bodyshop.fields.system_settings.local_media_server.token")}
>
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.autoemail")} grow style={{ marginBottom: 0 }}>
<Form.Item
key="attach_pdf_to_email"
name={["attach_pdf_to_email"]}
label={t("bodyshop.fields.system_settings.auto_email.attach_pdf_to_email")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
key="md_from_emails"
name={["md_from_emails"]}
label={t("bodyshop.fields.system_settings.auto_email.from_emails")}
>
<Select mode="tags" />
</Form.Item>
<Form.Item
key="md_email_cc_parts_order"
name={["md_email_cc", "parts_order"]}
label={t("bodyshop.fields.system_settings.auto_email.parts_order_cc")}
rules={[
{
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item
key="md_email_cc_parts_return_slip"
name={["md_email_cc", "parts_return_slip"]}
label={t("bodyshop.fields.system_settings.auto_email.parts_return_slip_cc")}
rules={[
{
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow
grow
style={{ marginBottom: 0 }}
title={
<div
style={{
...INLINE_TITLE_ROW_STYLE,
justifyContent: "space-between"
}}
>
<div
style={{
fontWeight: 500,
marginRight: "auto"
}}
>
{t("bodyshop.labels.jobcosting")}
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: 4,
flexWrap: "wrap",
marginLeft: "auto"
}}
>
<div aria-hidden style={INLINE_TITLE_SEPARATOR_STYLE} />
<div style={INLINE_TITLE_SWITCH_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>
{t("bodyshop.fields.system_settings.job_costing.use_paint_scale_data")}
</div>
<Form.Item
noStyle
key="use_paint_scale_data"
name={["use_paint_scale_data"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</div>
</div>
</div>
}
wrapTitle
>
<Form.Item
key="target_touchtime"
name={["target_touchtime"]}
label={t("bodyshop.fields.system_settings.job_costing.target_touch_time")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0.1} precision={1} />
</Form.Item>
<Form.Item
key="md_hour_split_prep"
label={t("bodyshop.fields.system_settings.job_costing.prep_hour_split")}
name={["md_hour_split", "prep"]}
dependencies={[["md_hour_split", "paint"]]}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value && !getFieldValue(["md_hour_split", "paint"])) {
return Promise.resolve();
}
if (value + getFieldValue(["md_hour_split", "paint"]) === 1) {
return Promise.resolve();
}
return Promise.reject(t("bodyshop.validation.larsplit"));
}
})
]}
>
<InputNumber min={0} max={1} precision={2} />
</Form.Item>
<Form.Item
key="md_hour_split_paint"
label={t("bodyshop.fields.system_settings.job_costing.paint_hour_split")}
name={["md_hour_split", "paint"]}
dependencies={[["md_hour_split", "prep"]]}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value && !getFieldValue(["md_hour_split", "paint"])) {
return Promise.resolve();
}
if (value + getFieldValue(["md_hour_split", "prep"]) === 1) {
return Promise.resolve();
}
return Promise.reject(t("bodyshop.validation.larsplit"));
}
})
]}
>
<InputNumber min={0} max={1} precision={2} />
</Form.Item>
<Form.Item
key="jc_hourly_rates_mapa"
label={t("bodyshop.fields.system_settings.job_costing.paint_materials_hourly_cost_rate")}
name={["jc_hourly_rates", "mapa"]}
>
<CurrencyInput prefix="$" />
</Form.Item>
<Form.Item
key="jc_hourly_rates_mash"
label={t("bodyshop.fields.system_settings.job_costing.shop_materials_hourly_cost_rate")}
name={["jc_hourly_rates", "mash"]}
>
<CurrencyInput prefix="$" />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow
header={t("bodyshop.labels.shop_enabled_features")}
id="sharing"
grow
style={{ marginBottom: 0 }}
>
<Form.Item
label={t("general.actions.sharetoteams")}
valuePropName="checked"
name={["md_functionality_toggles", "teams"]}
>
<Switch />
</Form.Item>
{isReynoldsMode && (
<Form.Item
initialValue
label={t("bodyshop.fields.md_functionality_toggles.enhanced_early_ros")}
name={["md_functionality_toggles", "enhanced_early_ros"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
)}
</LayoutFormRow>
</div>
</>
</LayoutFormRow>
<Form.List name={["md_messaging_presets"]}>
{(fields, { add, remove, move }) => {
return (
<LayoutFormRow
grow
header={t("bodyshop.labels.messagingpresets")}
id="messagingpresets"
actions={[
buildSectionActionButton("add-messaging-preset", t("bodyshop.actions.add_messaging_preset"), () => {
add();
})
]}
>
<div>
{renderListOrEmpty(fields, t("bodyshop.actions.add_messaging_preset"), () =>
fields.map((field, index) => {
return (
<Form.Item noStyle key={field.key}>
<InlineValidatedFormRow
form={form}
errorNames={[["md_messaging_presets", field.name, "label"]]}
noDivider
title={
<div style={INLINE_TITLE_ROW_STYLE}>
<InlineTitleListIcon style={INLINE_TITLE_HANDLE_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("bodyshop.fields.messaginglabel_short")}</div>
<Form.Item
noStyle
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("bodyshop.fields.messaginglabel_short")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
</div>
}
extra={
<Space align="center" size="small">
<Button
type="text"
danger
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("bodyshop.fields.messagingtext_short")}
name={[field.name, "text"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
style={{ marginBottom: 0 }}
>
<Input.TextArea
autoSize={{ minRows: 1, maxRows: 3 }}
placeholder={t("bodyshop.fields.messagingtext_short")}
/>
</Form.Item>
</InlineValidatedFormRow>
</Form.Item>
);
})
)}
</div>
</LayoutFormRow>
);
}}
</Form.List>
<Form.List name={["md_notes_presets"]}>
{(fields, { add, remove, move }) => {
return (
<LayoutFormRow
grow
header={t("bodyshop.labels.notespresets")}
id="notespresets"
actions={[
buildSectionActionButton("add-note-preset", t("bodyshop.actions.add_note_preset"), () => {
add();
})
]}
>
<div>
{renderListOrEmpty(fields, t("bodyshop.actions.add_note_preset"), () =>
fields.map((field, index) => {
return (
<Form.Item noStyle key={field.key}>
<InlineValidatedFormRow
form={form}
errorNames={[["md_notes_presets", field.name, "label"]]}
noDivider
title={
<div style={INLINE_TITLE_ROW_STYLE}>
<InlineTitleListIcon style={INLINE_TITLE_HANDLE_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("bodyshop.fields.noteslabel_short")}</div>
<Form.Item
noStyle
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("bodyshop.fields.noteslabel_short")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
</div>
}
extra={
<Space align="center" size="small">
<Button
type="text"
danger
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("bodyshop.fields.notestext_short")}
name={[field.name, "text"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
style={{ marginBottom: 0 }}
>
<Input.TextArea
autoSize={{ minRows: 1, maxRows: 3 }}
placeholder={t("bodyshop.fields.notestext_short")}
/>
</Form.Item>
</InlineValidatedFormRow>
</Form.Item>
);
})
)}
</div>
</LayoutFormRow>
);
}}
</Form.List>
<Form.List name={["md_parts_locations"]}>
{(fields, { add, remove, move }) => {
return (
<LayoutFormRow
grow
header={t("bodyshop.labels.partslocations")}
id="partslocations"
actions={[
buildSectionActionButton("add-parts-location", t("bodyshop.actions.addpartslocation"), () => {
add();
})
]}
>
<div>
{renderListOrEmpty(fields, t("bodyshop.actions.addpartslocation"), () =>
fields.map((field, index) => {
return (
<Form.Item noStyle key={field.key}>
<InlineValidatedFormRow
form={form}
errorNames={[["md_parts_locations", field.name]]}
noDivider
title={
<div style={INLINE_TITLE_ROW_STYLE}>
<InlineTitleListIcon style={INLINE_TITLE_HANDLE_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("bodyshop.fields.partslocation")}</div>
<Form.Item
noStyle
name={[field.name]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("bodyshop.fields.partslocation")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
</div>
}
extra={
<Space align="center" size="small">
<Button
type="text"
danger
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
/>
</Form.Item>
);
})
)}
</div>
</LayoutFormRow>
);
}}
</Form.List>
{/*Start Insurance Provider Row */}
<Form.List
name={["md_ins_cos"]}
rules={[
{
validator: async (_, companies = []) => {
const normalizedNames = (Array.isArray(companies) ? companies : [])
.map((company) => (company?.name ?? "").toString().trim().toLowerCase())
.filter(Boolean);
if (new Set(normalizedNames).size !== normalizedNames.length) {
throw new Error(t("bodyshop.errors.duplicate_insurance_company"));
}
}
}
]}
>
{(fields, { add, remove, move }) => {
return (
<LayoutFormRow
grow
header={<span id="insurancecos-header">{t("bodyshop.labels.insurancecos")}</span>}
id="insurancecos"
actions={[
buildSectionActionButton(
"add-insurance-company",
t("bodyshop.actions.add_insurance_company"),
() => {
add();
},
"insurancecos-add-button"
)
]}
>
<div>
{renderListOrEmpty(fields, t("bodyshop.actions.add_insurance_company"), () =>
fields.map((field, index) => {
const insuranceCompanyErrors = duplicateInsuranceCompanyIndexes.has(field.name)
? [t("bodyshop.errors.duplicate_insurance_company")]
: [];
return (
<Form.Item noStyle key={field.key}>
<InlineValidatedFormRow
form={form}
errorNames={[["md_ins_cos", field.name, "name"]]}
extraErrors={insuranceCompanyErrors}
noDivider
title={
<div style={INLINE_TITLE_ROW_STYLE}>
<InlineTitleListIcon style={INLINE_TITLE_HANDLE_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("bodyshop.fields.md_ins_co.name")}</div>
<Form.Item
noStyle
name={[field.name, "name"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("bodyshop.fields.md_ins_co.name")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
<div aria-hidden style={INLINE_TITLE_SEPARATOR_STYLE} />
<div style={INLINE_TITLE_SWITCH_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("bodyshop.fields.md_ins_co.private")}</div>
<Form.Item noStyle name={[field.name, "private"]} valuePropName="checked">
<Switch size="small" />
</Form.Item>
</div>
</div>
}
wrapTitle
extra={
<Space align="center" size="small">
<Button
type="text"
danger
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("bodyshop.fields.md_ins_co.street1")}
key={`${index}street1`}
name={[field.name, "street1"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.md_ins_co.street2")}
key={`${index}street2`}
name={[field.name, "street2"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.md_ins_co.city")}
key={`${index}city`}
name={[field.name, "city"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.md_ins_co.state")}
key={`${index}state`}
name={[field.name, "state"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.md_ins_co.zip")}
key={`${index}zip`}
name={[field.name, "zip"]}
>
<Input />
</Form.Item>
</InlineValidatedFormRow>
</Form.Item>
);
})
)}
</div>
</LayoutFormRow>
);
}}
</Form.List>
{/*End Insurance Provider Row */}
<Form.List name={["md_estimators"]}>
{(fields, { add, remove, move }) => {
return (
<LayoutFormRow
grow
header={t("bodyshop.labels.estimators")}
id="estimators"
actions={[
buildSectionActionButton("add-estimator", t("bodyshop.actions.add_estimator"), () => {
add();
})
]}
>
<div>
{renderListOrEmpty(fields, t("bodyshop.actions.add_estimator"), () =>
fields.map((field, index) => {
return (
<Form.Item noStyle key={field.key}>
<InlineValidatedFormRow
form={form}
errorNames={[
["md_estimators", field.name, "est_ct_fn"],
["md_estimators", field.name, "est_ct_ln"],
["md_estimators", field.name, "est_co_nm"]
]}
noDivider
title={
<div style={INLINE_TITLE_ROW_STYLE}>
<InlineTitleListIcon style={INLINE_TITLE_HANDLE_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("jobs.fields.est_ct_fn_short")}</div>
<Form.Item noStyle name={[field.name, "est_ct_fn"]}>
<Input
size="small"
placeholder={t("jobs.fields.est_ct_fn_short")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
<div aria-hidden style={INLINE_TITLE_SEPARATOR_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("jobs.fields.est_ct_ln_short")}</div>
<Form.Item noStyle name={[field.name, "est_ct_ln"]}>
<Input
size="small"
placeholder={t("jobs.fields.est_ct_ln_short")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
<div aria-hidden style={INLINE_TITLE_SEPARATOR_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("jobs.fields.est_co_nm_short")}</div>
<Form.Item noStyle name={[field.name, "est_co_nm"]}>
<Input
size="small"
placeholder={t("jobs.fields.est_co_nm_short")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
</div>
}
wrapTitle
extra={
<Space align="center" size="small">
<Button
type="text"
danger
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("jobs.fields.est_ph1_short")}
key={`${index}est_ph1`}
name={[field.name, "est_ph1"]}
rules={[
({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, [field.name, "est_ph"])
]}
>
<PhoneFormItem formatDisplayOnly showPhoneAction />
</Form.Item>
<Form.Item
label={t("jobs.fields.est_ea_short")}
key={`${index}est_ea`}
name={[field.name, "est_ea"]}
rules={[
{
type: "email",
message: "This is not a valid email address."
}
]}
>
<FormItemEmail email={form.getFieldValue([field.name, "est_ea"])} />
</Form.Item>
</InlineValidatedFormRow>
</Form.Item>
);
})
)}
</div>
</LayoutFormRow>
);
}}
</Form.List>
<Form.List name={["md_filehandlers"]}>
{(fields, { add, remove, move }) => {
return (
<LayoutFormRow
grow
header={t("bodyshop.labels.filehandlers")}
id="filehandlers"
actions={[
buildSectionActionButton("add-adjuster", t("bodyshop.actions.add_adjuster"), () => {
add();
})
]}
>
<div>
{renderListOrEmpty(fields, t("bodyshop.actions.add_adjuster"), () =>
fields.map((field, index) => {
return (
<Form.Item noStyle key={field.key}>
<LayoutFormRow
noDivider
title={
<div style={INLINE_TITLE_ROW_STYLE}>
<InlineTitleListIcon style={INLINE_TITLE_HANDLE_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("jobs.fields.ins_ct_fn_short")}</div>
<Form.Item noStyle name={[field.name, "ins_ct_fn"]}>
<Input
size="small"
placeholder={t("jobs.fields.ins_ct_fn_short")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
<div aria-hidden style={INLINE_TITLE_SEPARATOR_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("jobs.fields.ins_ct_ln_short")}</div>
<Form.Item noStyle name={[field.name, "ins_ct_ln"]}>
<Input
size="small"
placeholder={t("jobs.fields.ins_ct_ln_short")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
</div>
}
wrapTitle
extra={
<Space align="center" size="small">
<Button
type="text"
danger
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("jobs.fields.ins_ph1_short")}
key={`${index}ins_ph1`}
name={[field.name, "ins_ph1"]}
rules={[
({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, [field.name, "ins_ph"])
]}
>
<PhoneFormItem formatDisplayOnly showPhoneAction />
</Form.Item>
<Form.Item
label={t("jobs.fields.ins_ea_short")}
key={`${index}ins_ea`}
name={[field.name, "ins_ea"]}
rules={[
{
type: "email",
message: "This is not a valid email address."
}
]}
>
<FormItemEmail email={form.getFieldValue([field.name, "ins_ea"])} />
</Form.Item>
</LayoutFormRow>
</Form.Item>
);
})
)}
</div>
</LayoutFormRow>
);
}}
</Form.List>
<FeatureWrapper featureName="courtesycars" noauth={() => null}>
<Form.List name={["md_ccc_rates"]}>
{(fields, { add, remove, move }) => {
return (
<LayoutFormRow
grow
header={t("bodyshop.fields.md_ccc_rates")}
id="md_ccc_rates"
actions={[
buildSectionActionButton(
"add-courtesy-car-rate-preset",
t("bodyshop.actions.add_courtesy_car_rate_preset"),
() => {
add();
}
)
]}
>
<div>
{renderListOrEmpty(fields, t("bodyshop.actions.add_courtesy_car_rate_preset"), () =>
fields.map((field, index) => {
return (
<Form.Item noStyle key={field.key}>
<InlineValidatedFormRow
form={form}
errorNames={[["md_ccc_rates", field.name, "label"]]}
noDivider
title={
<div style={INLINE_TITLE_ROW_STYLE}>
<InlineTitleListIcon style={INLINE_TITLE_HANDLE_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("general.labels.label")}</div>
<Form.Item
noStyle
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("general.labels.label")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
</div>
}
wrapTitle
extra={
<Space align="center" size="small">
<Button
type="text"
danger
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("contracts.fields.actax")}
key={`${index}actax`}
name={[field.name, "actax"]}
>
<InputNumber precision={2} suffix="%" />
</Form.Item>
<Form.Item
label={t("contracts.fields.dailyfreekm")}
key={`${index}dailyfreekm`}
name={[field.name, "dailyfreekm"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.refuelcharge")}
key={`${index}refuelcharge`}
name={[field.name, "refuelcharge"]}
>
<CurrencyInput precision={2} min={0} prefix="$" />
</Form.Item>
<Form.Item
label={t("contracts.fields.excesskmrate")}
key={`${index}excesskmrate`}
name={[field.name, "excesskmrate"]}
>
<CurrencyInput precision={2} min={0} prefix="$" />
</Form.Item>
<Form.Item
label={t("contracts.fields.cleanupcharge")}
key={`${index}cleanupcharge`}
name={[field.name, "cleanupcharge"]}
>
<CurrencyInput precision={2} min={0} prefix="$" />
</Form.Item>
<Form.Item
label={t("contracts.fields.damagewaiver")}
key={`${index}damagewaiver`}
name={[field.name, "damagewaiver"]}
>
<CurrencyInput precision={2} min={0} prefix="$" />
</Form.Item>
<Form.Item
label={t("contracts.fields.federaltax")}
key={`${index}federaltax`}
name={[field.name, "federaltax"]}
>
<InputNumber precision={2} suffix="%" />
</Form.Item>
<Form.Item
label={t("contracts.fields.statetax")}
key={`${index}statetax`}
name={[field.name, "statetax"]}
>
<InputNumber precision={2} suffix="%" />
</Form.Item>
<Form.Item
label={t("contracts.fields.localtax")}
key={`${index}localtax`}
name={[field.name, "localtax"]}
>
<InputNumber precision={2} suffix="%" />
</Form.Item>
<Form.Item
label={t("contracts.fields.coverage")}
key={`${index}coverage`}
name={[field.name, "coverage"]}
>
<CurrencyInput precision={2} min={0} prefix="$" />
</Form.Item>
</InlineValidatedFormRow>
</Form.Item>
);
})
)}
</div>
</LayoutFormRow>
);
}}
</Form.List>
</FeatureWrapper>
<Form.List name={["md_jobline_presets"]}>
{(fields, { add, remove, move }) => {
return (
<LayoutFormRow
grow
header={t("bodyshop.fields.md_jobline_presets")}
id="md_jobline_presets"
actions={[
buildSectionActionButton("add-jobline-preset", t("bodyshop.actions.add_jobline_preset"), () => {
add();
})
]}
>
<div>
{renderListOrEmpty(fields, t("bodyshop.actions.add_jobline_preset"), () =>
fields.map((field, index) => {
return (
<Form.Item noStyle key={field.key}>
<InlineValidatedFormRow
form={form}
errorNames={[
["md_jobline_presets", field.name, "label"],
["md_jobline_presets", field.name, "line_desc"]
]}
noDivider
title={
<div style={INLINE_TITLE_ROW_STYLE}>
<InlineTitleListIcon style={INLINE_TITLE_HANDLE_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("general.labels.label")}</div>
<Form.Item
noStyle
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("general.labels.label")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
<div aria-hidden style={INLINE_TITLE_SEPARATOR_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("joblines.fields.line_desc")}</div>
<Form.Item noStyle name={[field.name, "line_desc"]}>
<Input.TextArea
autoSize={{ minRows: 1, maxRows: 3 }}
placeholder={t("joblines.fields.line_desc")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%",
paddingBlock: 5
}}
/>
</Form.Item>
</div>
</div>
}
wrapTitle
extra={
<Space align="center" size="small">
<Button
type="text"
danger
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("joblines.fields.mod_lbr_ty")}
key={`${index}mod_lbr_ty`}
name={[field.name, "mod_lbr_ty"]}
>
<Select
allowClear
options={[
{ value: "LAA", label: t("joblines.fields.lbr_types.LAA") },
{ value: "LAB", label: t("joblines.fields.lbr_types.LAB") },
{ value: "LAD", label: t("joblines.fields.lbr_types.LAD") },
{ value: "LAE", label: t("joblines.fields.lbr_types.LAE") },
{ value: "LAF", label: t("joblines.fields.lbr_types.LAF") },
{ value: "LAG", label: t("joblines.fields.lbr_types.LAG") },
{ value: "LAM", label: t("joblines.fields.lbr_types.LAM") },
{ value: "LAR", label: t("joblines.fields.lbr_types.LAR") },
{ value: "LAS", label: t("joblines.fields.lbr_types.LAS") },
{ value: "LAU", label: t("joblines.fields.lbr_types.LAU") },
{ value: "LA1", label: t("joblines.fields.lbr_types.LA1") },
{ value: "LA2", label: t("joblines.fields.lbr_types.LA2") },
{ value: "LA3", label: t("joblines.fields.lbr_types.LA3") },
{ value: "LA4", label: t("joblines.fields.lbr_types.LA4") }
]}
/>
</Form.Item>
<Form.Item
label={t("joblines.fields.mod_lb_hrs")}
key={`${index}mod_lb_hrs`}
name={[field.name, "mod_lb_hrs"]}
>
<InputNumber precision={1} suffix="hrs" />
</Form.Item>
<Form.Item
label={t("joblines.fields.part_type")}
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<Select
allowClear
options={[
{ value: "PAA", label: t("joblines.fields.part_types.PAA") },
{ value: "PAC", label: t("joblines.fields.part_types.PAC") },
{ value: "PAE", label: t("joblines.fields.part_types.PAE") },
{ value: "PAL", label: t("joblines.fields.part_types.PAL") },
{ value: "PAM", label: t("joblines.fields.part_types.PAM") },
{ value: "PAN", label: t("joblines.fields.part_types.PAN") },
{ value: "PAO", label: t("joblines.fields.part_types.PAO") },
{ value: "PAR", label: t("joblines.fields.part_types.PAR") },
{ value: "PAS", label: t("joblines.fields.part_types.PAS") }
]}
/>
</Form.Item>
<Form.Item
label={t("joblines.fields.oem_partno")}
key={`${index}oem_partno`}
name={[field.name, "oem_partno"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("joblines.fields.part_qty")}
key={`${index}part_qty`}
name={[field.name, "part_qty"]}
>
<CurrencyInput precision={2} min={0} />
</Form.Item>
<Form.Item
label={t("joblines.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<CurrencyInput precision={2} min={0} prefix="$" />
</Form.Item>
<Form.Item
label={t("joblines.fields.prt_dsmk_p")}
key={`${index}prt_dsmk_p`}
name={[field.name, "prt_dsmk_p"]}
>
<InputNumber precision={0} min={-100} max={100} suffix="%" />
</Form.Item>
<Form.Item
label={t("joblines.fields.ah_detail_line")}
key={`${index}ah_detail_line`}
name={[field.name, "ah_detail_line"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</InlineValidatedFormRow>
</Form.Item>
);
})
)}
</div>
</LayoutFormRow>
);
}}
</Form.List>
<Form.List name={["md_parts_order_comment"]}>
{(fields, { add, remove, move }) => {
return (
<LayoutFormRow
grow
header={t("bodyshop.fields.md_parts_order_comment")}
id="md_parts_order_comment"
actions={[
buildSectionActionButton(
"add-parts-order-comment",
t("bodyshop.actions.add_parts_order_comment"),
() => {
add();
}
)
]}
>
<div>
{renderListOrEmpty(fields, t("bodyshop.actions.add_parts_order_comment"), () =>
fields.map((field, index) => {
return (
<Form.Item noStyle key={field.key}>
<InlineValidatedFormRow
form={form}
errorNames={[["md_parts_order_comment", field.name, "label"]]}
noDivider
title={
<div style={INLINE_TITLE_ROW_STYLE}>
<InlineTitleListIcon style={INLINE_TITLE_HANDLE_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("general.labels.label")}</div>
<Form.Item
noStyle
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("general.labels.label")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
</div>
}
wrapTitle
extra={
<Space align="center" size="small">
<Button
type="text"
danger
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("parts_orders.fields.comments")}
name={[field.name, "comment"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
style={{ marginBottom: 0 }}
>
<Input.TextArea
autoSize={{ minRows: 1, maxRows: 3 }}
placeholder={t("parts_orders.fields.comments")}
/>
</Form.Item>
</InlineValidatedFormRow>
</Form.Item>
);
})
)}
</div>
</LayoutFormRow>
);
}}
</Form.List>
<Form.List name={["md_to_emails"]}>
{(fields, { add, remove, move }) => {
return (
<LayoutFormRow
grow
header={t("bodyshop.labels.md_to_emails")}
id="md_to_emails"
actions={[
buildSectionActionButton("add-to-email-preset", t("bodyshop.actions.add_to_email_preset"), () => {
add();
})
]}
>
<div>
{renderListOrEmpty(fields, t("bodyshop.actions.add_to_email_preset"), () =>
fields.map((field, index) => {
return (
<Form.Item noStyle key={field.key}>
<InlineValidatedFormRow
form={form}
errorNames={[["md_to_emails", field.name, "label"]]}
noDivider
title={
<div style={INLINE_TITLE_ROW_STYLE}>
<InlineTitleListIcon style={INLINE_TITLE_HANDLE_STYLE} />
<div style={INLINE_TITLE_GROUP_STYLE}>
<div style={INLINE_TITLE_LABEL_STYLE}>{t("general.labels.label")}</div>
<Form.Item
noStyle
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("general.labels.label")}
style={{
...INLINE_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
</div>
}
wrapTitle
extra={
<Space align="center" size="small">
<Button
type="text"
danger
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("bodyshop.labels.md_to_emails_emails")}
name={[field.name, "emails"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
style={{ marginBottom: 0 }}
>
<FormItemEmail email={form.getFieldValue([field.name, "emails"])} />
</Form.Item>
</InlineValidatedFormRow>
</Form.Item>
);
})
)}
</div>
</LayoutFormRow>
);
}}
</Form.List>
</div>
);
}
function getDuplicateIndexSetByNormalizedName(list, key) {
const indexes = new Set();
const firstIndexByValue = new Map();
(Array.isArray(list) ? list : []).forEach((item, index) => {
const normalizedValue = (item?.[key] ?? "").toString().trim().toLowerCase();
if (!normalizedValue) return;
if (firstIndexByValue.has(normalizedValue)) {
indexes.add(firstIndexByValue.get(normalizedValue));
indexes.add(index);
return;
}
firstIndexByValue.set(normalizedValue, index);
});
return indexes;
}