IO-3624 Refresh shop config list rows and color UX

This commit is contained in:
Dave
2026-03-24 10:54:42 -04:00
parent f904fa4e18
commit d23a182650
22 changed files with 3100 additions and 1809 deletions

View File

@@ -17,7 +17,7 @@ export default function FormsFieldChanged({ form, skipPrompt }) {
const errors = form.getFieldsError().filter((e) => e.errors.length > 0);
if (form.isFieldsTouched())
return (
<Space orientation="vertical" style={{ width: "100%" }}>
<Space orientation="vertical" style={{ width: "100%", marginBottom: 10 }}>
<Prompt when={!skipPrompt} beforeUnload={true} message={t("general.messages.unsavedchangespopup")} />
<AlertComponent
type="warning"

View File

@@ -1,11 +1,88 @@
import { Input } from "antd";
import { PhoneFilled } from "@ant-design/icons";
import { Button, Input, Space } from "antd";
import i18n from "i18next";
import parsePhoneNumber from "libphonenumber-js";
import { forwardRef, useMemo, useState } from "react";
import "./phone-form-item.styles.scss";
function FormItemPhone({ ref, ...props }) {
return <Input ref={ref} {...props} />;
/**
* Formats a phone number for display purposes. If the input value is a valid phone number, it will be formatted in a
* national format (e.g., (123) 456-7890 for US/CA). If the input is not a valid phone number, it will be returned as-is.
* @param value
* @returns {*}
*/
const formatPhoneDisplayValue = (value) => {
if (!value) return value;
try {
const parsedPhone = parsePhoneNumber(value, "CA");
return parsedPhone?.isValid() ? parsedPhone.formatNational() : value;
} catch {
return value;
}
};
/**
* Generates a "tel:" URL for a phone number if it's valid. If the input value is a valid phone number, it will return a
* URL in the format "tel:+1234567890". If the input is not a valid phone number, it will attempt to trim whitespace and
* return a "tel:" URL with the raw value, or null if the trimmed value is empty.
* @param value
* @returns {string|null}
*/
const getPhoneActionHref = (value) => {
if (!value) return null;
try {
const parsedPhone = parsePhoneNumber(value, "CA");
if (parsedPhone?.isValid()) return `tel:${parsedPhone.number}`;
} catch {
// Fall back to the raw value below.
}
const trimmedValue = String(value).trim();
return trimmedValue ? `tel:${trimmedValue}` : null;
};
const FormItemPhone = forwardRef(function FormItemPhone(
{ formatDisplayOnly = false, showPhoneAction = false, value, onBlur, onFocus, ...props },
ref
) {
const [isFocused, setIsFocused] = useState(false);
const displayValue = useMemo(() => {
if (!formatDisplayOnly || isFocused) return value;
return formatPhoneDisplayValue(value);
}, [formatDisplayOnly, isFocused, value]);
const phoneActionHref = useMemo(() => (showPhoneAction ? getPhoneActionHref(value) : null), [showPhoneAction, value]);
const input = (
<Input
ref={ref}
{...props}
value={displayValue}
onFocus={(event) => {
setIsFocused(true);
onFocus?.(event);
}}
onBlur={(event) => {
setIsFocused(false);
onBlur?.(event);
}}
/>
);
if (!showPhoneAction) return input;
return (
<Space.Compact style={{ width: "100%" }}>
{input}
{phoneActionHref ? (
<Button icon={<PhoneFilled />} href={phoneActionHref} />
) : (
<Button icon={<PhoneFilled />} disabled />
)}
</Space.Compact>
);
});
export default FormItemPhone;

View File

@@ -0,0 +1,30 @@
/**
* Normalize Form Item List Titles
* @param value
* @returns {*|string}
*/
const normalizeFormListTitleValue = (value) => {
if (value === null || value === undefined) return "";
if (Array.isArray(value)) {
return value
.map((item) => normalizeFormListTitleValue(item))
.filter(Boolean)
.join(", ");
}
return String(value).trim();
};
/**
* Get Form Listem Item Title
* @param fallbackLabel
* @param index
* @param candidates
* @returns {*|string}
*/
export function getFormListItemTitle(fallbackLabel, index, ...candidates) {
const title = candidates.map((candidate) => normalizeFormListTitleValue(candidate)).find(Boolean);
return title || `${fallbackLabel} ${index + 1}`;
}

View File

@@ -7,32 +7,37 @@ export default function LayoutFormRow({
children,
grow = false,
noDivider = false,
gutter = [16, 16], // Responsive gutter: horizontal, vertical
gutter,
rowProps,
// Optional overrides if you ever need per-section customization
surface = true,
surfaceBg,
surfaceHeaderBg,
surfaceBorderColor,
...cardProps
}) {
const items = Children.toArray(children).filter(Boolean);
if (items.length === 0) return null;
const isCompactRow = noDivider;
const title = !noDivider && header ? header : undefined;
const resolvedGutter = gutter ?? [16, isCompactRow ? 8 : 16];
const bg = surfaceBg ?? (surface ? "var(--imex-form-surface)" : undefined);
const headBg = surfaceHeaderBg ?? (surface ? "var(--imex-form-surface-head)" : undefined);
const borderColor = surfaceBorderColor ?? (surface ? "var(--imex-form-surface-border)" : undefined);
const mergedStyles = mergeSemanticStyles(
{
header: {
paddingInline: 16,
background: headBg
paddingInline: isCompactRow ? 12 : 16,
background: headBg,
borderBottomColor: borderColor
},
body: {
padding: 16,
padding: isCompactRow ? 12 : 16,
background: bg
}
},
@@ -40,8 +45,9 @@ export default function LayoutFormRow({
);
const baseCardStyle = {
marginBottom: ".8rem",
marginBottom: isCompactRow ? "8px" : ".8rem",
...(bg ? { background: bg } : null), // ensures the “circled area” is tinted
...(borderColor ? { borderColor } : null),
...cardProps.style
};
@@ -53,7 +59,9 @@ export default function LayoutFormRow({
title={cardProps.title ?? title}
size={cardProps.size ?? "small"}
variant={cardProps.variant ?? "outlined"}
className={["imex-form-row", cardProps.className].filter(Boolean).join(" ")}
className={["imex-form-row", isCompactRow ? "imex-form-row--compact" : null, cardProps.className]
.filter(Boolean)
.join(" ")}
style={baseCardStyle}
styles={mergedStyles}
>
@@ -128,11 +136,13 @@ export default function LayoutFormRow({
title={cardProps.title ?? title}
size={cardProps.size ?? "small"}
variant={cardProps.variant ?? "outlined"}
className={["imex-form-row", cardProps.className].filter(Boolean).join(" ")}
className={["imex-form-row", isCompactRow ? "imex-form-row--compact" : null, cardProps.className]
.filter(Boolean)
.join(" ")}
style={baseCardStyle}
styles={mergedStyles}
>
<Row gutter={gutter} wrap {...rowProps}>
<Row gutter={resolvedGutter} wrap {...rowProps}>
{items.map((child, idx) => (
<Col key={child?.key ?? idx} {...getColPropsForChild(child)}>
{child}

View File

@@ -43,10 +43,29 @@ html[data-theme="dark"] {
border-bottom-color: var(--imex-form-surface-border);
}
&.imex-form-row--compact {
.ant-card-head {
min-height: 40px;
}
.ant-card-head-title,
.ant-card-extra {
padding-block: 2px;
}
.ant-form-item {
margin-bottom: 12px;
}
}
.ant-card-body {
background: var(--imex-form-surface);
}
.ant-form-item:last-child {
margin-bottom: 4px;
}
/* Optional: tighter spacing on phones for better space usage */
@media (max-width: 575px) {
.ant-card-head {
@@ -70,6 +89,10 @@ html[data-theme="dark"] {
width: 100%;
}
.ant-form-item:has(> .imex-form-row--compact) {
margin-bottom: 8px;
}
/* Better form item spacing on mobile */
@media (max-width: 575px) {
.ant-form-item {

View File

@@ -1,12 +1,13 @@
import { DeleteFilled, DownOutlined, WarningFilled } from "@ant-design/icons";
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import { Checkbox, Divider, Dropdown, Form, Input, InputNumber, Radio, Select, Space, Tag } from "antd";
import { Button, Checkbox, Divider, Dropdown, Form, Input, InputNumber, Radio, Select, Space, Tag } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyInput from "../form-items-formatted/currency-form-item.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
@@ -50,6 +51,7 @@ export function PartsOrderModalComponent({
});
const { t } = useTranslation();
const partsOrderLines = Form.useWatch(["parts_order_lines", "data"], form) || [];
const handleClick = ({ item }) => {
form.setFieldsValue({ comments: item.props.value });
};
@@ -128,10 +130,38 @@ export function PartsOrderModalComponent({
{(fields, { remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const partsOrderLine = partsOrderLines[field.name] || {};
return (
<Form.Item required={false} key={field.key}>
<div style={{ display: "flex" }}>
<LayoutFormRow grow noDivider style={{ flex: 1 }}>
<LayoutFormRow
grow
noDivider
title={getFormListItemTitle(
t("parts_orders.fields.line_desc"),
index,
partsOrderLine.line_desc,
partsOrderLine.oem_partno
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
//span={8}
label={t("parts_orders.fields.line_desc")}
@@ -220,20 +250,9 @@ export function PartsOrderModalComponent({
</Form.Item>
)}
</LayoutFormRow>
<Space wrap size="small" align="center">
<div>
<DeleteFilled
style={{ margin: "1rem" }}
onClick={() => {
remove(field.name);
}}
/>
</div>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</div>
</Form.Item>
))}
);
})}
</div>
);
}}

View File

@@ -1,10 +1,11 @@
import { DeleteFilled } from "@ant-design/icons";
import { Form, Input, InputNumber, Select, Typography } from "antd";
import { Button, Form, Input, InputNumber, Select, Space, Typography } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
@@ -15,6 +16,7 @@ export default connect(mapStateToProps, null)(PartsReceiveModalComponent);
export function PartsReceiveModalComponent({ bodyshop, form }) {
const { t } = useTranslation();
const partsOrderLines = Form.useWatch(["partsorderlines"], form) || [];
return (
<div>
@@ -42,16 +44,43 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
{(fields, { remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const partsOrderLine = partsOrderLines[field.name] || {};
return (
<Form.Item required={false} key={field.key}>
<div style={{ display: "flex", alignItems: "center" }}>
<Form.Item hidden key={`${index}joblineid`} name={[field.name, "joblineid"]}>
<Input />
</Form.Item>
<Form.Item hidden key={`${index}id`} name={[field.name, "id"]}>
<Input />
</Form.Item>
<LayoutFormRow grow style={{ flex: 1 }}>
<LayoutFormRow
grow
title={getFormListItemTitle(
t("parts_orders.fields.line_desc"),
index,
partsOrderLine.line_desc,
partsOrderLine.oem_partno
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("parts_orders.fields.line_desc")}
key={`${index}line_desc`}
@@ -101,16 +130,9 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
<InputNumber min={0} />
</Form.Item>
</LayoutFormRow>
<DeleteFilled
style={{ margin: "1rem" }}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</div>
</Form.Item>
))}
);
})}
</div>
);
}}

View File

@@ -2,10 +2,13 @@ import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Input, Select, Space } from "antd";
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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function PartsEmailPresetsComponent() {
const { t } = useTranslation();
const form = Form.useFormInstance();
const emailPresets = Form.useWatch(["md_to_emails"], form) || [];
return (
<div>
@@ -14,9 +17,32 @@ export default function PartsEmailPresetsComponent() {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const preset = emailPresets[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(t("general.labels.label"), index, preset.label, preset.emails)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
<Input />
</Form.Item>
@@ -27,18 +53,10 @@ export default function PartsEmailPresetsComponent() {
>
<Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item>
<Space>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"

View File

@@ -2,10 +2,13 @@ import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Input, Space } from "antd";
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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function PartsLocationsComponent() {
const { t } = useTranslation();
const form = Form.useFormInstance();
const partsLocations = Form.useWatch(["md_parts_locations"], form) || [];
return (
<div>
@@ -14,9 +17,32 @@ export default function PartsLocationsComponent() {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const location = partsLocations[field.name];
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(t("bodyshop.fields.partslocation"), index, location)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
className="imex-flex-row__margin"
label={t("bodyshop.fields.partslocation")}
@@ -30,18 +56,10 @@ export default function PartsLocationsComponent() {
>
<Input />
</Form.Item>
<Space wrap>
<DeleteFilled
className="imex-flex-row__margin"
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"

View File

@@ -2,10 +2,13 @@ import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Input, Space } from "antd";
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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function PartsOrderCommentsComponent() {
const { t } = useTranslation();
const form = Form.useFormInstance();
const orderComments = Form.useWatch(["md_parts_order_comment"], form) || [];
return (
<div>
@@ -14,9 +17,37 @@ export default function PartsOrderCommentsComponent() {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const comment = orderComments[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("parts_orders.fields.comments"),
index,
comment.label,
comment.comment
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
@@ -41,18 +72,10 @@ export default function PartsOrderCommentsComponent() {
>
<Input.TextArea autoSize />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"

View File

@@ -1,7 +1,7 @@
import { DeleteFilled } from "@ant-design/icons";
import { useApolloClient, useMutation, useQuery } from "@apollo/client/react";
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import { Button, Card, Form, Input, InputNumber, Select, Switch } from "antd";
import { Button, Card, Form, Input, InputNumber, Select, Space, Switch } from "antd";
import ResponsiveTable from "../responsive-table/responsive-table.component";
import { useForm } from "antd/es/form/Form";
import queryString from "query-string";
@@ -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 { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
@@ -40,6 +41,7 @@ const mapDispatchToProps = () => ({
export function ShopEmployeesFormComponent({ bodyshop }) {
const { t } = useTranslation();
const [form] = useForm();
const employeeRates = Form.useWatch(["rates"], form) || [];
const history = useNavigate();
const search = queryString.parse(useLocation().search);
const [deleteVacation] = useMutation(DELETE_VACATION);
@@ -311,9 +313,32 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const employeeRate = employeeRates[field.name] || {};
return (
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
<LayoutFormRow grow>
<LayoutFormRow
grow
title={getFormListItemTitle(t("employees.fields.cost_center"), index, employeeRate.cost_center)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("employees.fields.cost_center")}
key={`${index}`}
@@ -354,15 +379,10 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
>
<InputNumber min={0} precision={2} />
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"

View File

@@ -0,0 +1,304 @@
/**
* Default translucent card color used for tinting card surfaces when no specific color is provided.
* @type {{r: number, g: number, b: number, a: number}}
*/
export const DEFAULT_TRANSLUCENT_CARD_COLOR = {
r: 22,
g: 119,
b: 255,
a: 0.5
};
/**
* Rounds a color channel value to two decimal places.
* @param value
* @returns {number}
*/
const roundColorChannel = (value) => Math.round(value * 100) / 100;
/**
* Rounds a tint percentage value to two decimal places.
* @param value
* @returns {number}
*/
const roundTintPercentage = (value) => Math.round(value * 100) / 100;
/**
* Clamps an alpha value to the range [0, 1] and rounds it to two decimal places.
* @param value
* @returns {number}
*/
const clampAlpha = (value) => {
const numericValue = Number(value);
if (!Number.isFinite(numericValue)) return 1;
if (numericValue <= 0) return 0;
if (numericValue >= 1) return 1;
return numericValue;
};
/**
* Converts an RGB color object to a hexadecimal color string.
* @param param0
* @param param0.r
* @param param0.g
* @param param0.b
* @returns {`#${string}`}
*/
const rgbToHex = ({ r, g, b }) =>
`#${[r, g, b].map((channel) => Math.round(channel).toString(16).padStart(2, "0")).join("")}`;
/**
* Converts an RGB color object to an HSL color object.
* @param param0
* @param param0.r
* @param param0.g
* @param param0.b
* @param param0.a
* @returns {{h: number, s: number, l: number, a: number}|{h: number, s: number, l: number, a: number}}
*/
const rgbToHsl = ({ r, g, b, a = 1 }) => {
const red = r / 255;
const green = g / 255;
const blue = b / 255;
const max = Math.max(red, green, blue);
const min = Math.min(red, green, blue);
const delta = max - min;
const lightness = (max + min) / 2;
if (delta === 0) {
return { h: 0, s: 0, l: roundColorChannel(lightness), a };
}
const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);
let hue;
switch (max) {
case red:
hue = (green - blue) / delta + (green < blue ? 6 : 0);
break;
case green:
hue = (blue - red) / delta + 2;
break;
default:
hue = (red - green) / delta + 4;
break;
}
return {
h: roundColorChannel(hue * 60),
s: roundColorChannel(saturation),
l: roundColorChannel(lightness),
a
};
};
/**
* Converts an RGB color object to an HSV color object.
* @param param0
* @param param0.r
* @param param0.g
* @param param0.b
* @param param0.a
* @returns {{h: number, s: number, v: number, a: number}}
*/
const rgbToHsv = ({ r, g, b, a = 1 }) => {
const red = r / 255;
const green = g / 255;
const blue = b / 255;
const max = Math.max(red, green, blue);
const min = Math.min(red, green, blue);
const delta = max - min;
const saturation = max === 0 ? 0 : delta / max;
let hue = 0;
if (delta !== 0) {
switch (max) {
case red:
hue = (green - blue) / delta + (green < blue ? 6 : 0);
break;
case green:
hue = (blue - red) / delta + 2;
break;
default:
hue = (red - green) / delta + 4;
break;
}
}
return {
h: roundColorChannel(hue * 60),
s: roundColorChannel(saturation),
v: roundColorChannel(max),
a
};
};
/**
* Builds a comprehensive color value object for a color picker component based on an input RGB color object.
* @param rgb
* @returns {{hex: `#${string}`, rgb: *, hsl: {h: number, s: number, l: number, a: number}, hsv: {h: number, s: number, v: number, a: number}, oldHue: number, source: string}}
*/
const buildPickerColorValue = (rgb) => {
const hsl = rgbToHsl(rgb);
return {
hex: rgbToHex(rgb),
rgb: { ...rgb },
hsl,
hsv: rgbToHsv(rgb),
oldHue: hsl.h,
source: "rgb"
};
};
/**
* Default color value object for the color picker component, derived from the default translucent card color.
* @type {{hex: `#${string}`, rgb: *, hsl: {h: number, s: number, l: number, a: number}, hsv: {h: number, s: number, v: number, a: number}, oldHue: number, source: string}}
*/
export const DEFAULT_TRANSLUCENT_PICKER_COLOR = buildPickerColorValue(DEFAULT_TRANSLUCENT_CARD_COLOR);
/**
* Parses a color string that may be a JSON representation of a color object. If the string is valid JSON and represents
* a color, it returns the parsed object; otherwise, it returns the original string.
* @param color
* @returns {*|string}
*/
const parseJsonColorString = (color) => {
if (typeof color !== "string") return color;
const trimmedColor = color.trim();
if (!trimmedColor.startsWith("{") && !trimmedColor.startsWith("[")) return color;
try {
return JSON.parse(trimmedColor);
} catch {
return color;
}
};
/**
* Parses a hexadecimal color string (e.g., "#RRGGBB" or "#RRGGBBAA") and returns an object containing the corresponding
* RGB color value and alpha transparency. Supports both 3/4-digit and 6/8-digit hex formats.
* @param color
* @returns {{colorCssValue: string, alpha: number}|null}
*/
const parseHexColor = (color) => {
if (typeof color !== "string") return null;
const normalizedHex = color.trim().replace(/^#/, "");
if (![3, 4, 6, 8].includes(normalizedHex.length) || /[^0-9a-f]/i.test(normalizedHex)) {
return null;
}
const expandedHex =
normalizedHex.length <= 4
? normalizedHex
.split("")
.map((character) => `${character}${character}`)
.join("")
: normalizedHex;
const hasAlpha = expandedHex.length === 8;
const red = Number.parseInt(expandedHex.slice(0, 2), 16);
const green = Number.parseInt(expandedHex.slice(2, 4), 16);
const blue = Number.parseInt(expandedHex.slice(4, 6), 16);
const alpha = hasAlpha ? Number.parseInt(expandedHex.slice(6, 8), 16) / 255 : 1;
return {
colorCssValue: `rgb(${red}, ${green}, ${blue})`,
alpha: clampAlpha(alpha)
};
};
/**
* Parses an RGB or RGBA color string (e.g., "rgb(255, 0, 0)" or "rgba(255, 0, 0, 0.5)") and returns an object
* containing the corresponding RGB color value and alpha transparency. Supports both integer and percentage formats for
* color channels and alpha.
* @param color
* @returns {{colorCssValue: string, alpha: number}|null}
*/
const parseRgbColor = (color) => {
if (typeof color !== "string") return null;
const rgbMatch = color.trim().match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)$/i);
if (!rgbMatch) return null;
const [, red, green, blue, alpha = 1] = rgbMatch;
return {
colorCssValue: `rgb(${red}, ${green}, ${blue})`,
alpha: clampAlpha(alpha)
};
};
/**
* Normalizes a color input into a consistent descriptor object containing a CSS color value and an alpha transparency
* level.
* @param color
* @returns {{colorCssValue: string, alpha: number}|{colorCssValue: string, alpha: number}|*|{colorCssValue: string, alpha: number}|null}
*/
const getNormalizedColorDescriptor = (color) => {
if (!color) return null;
const normalizedColor = parseJsonColorString(color);
if (typeof normalizedColor === "string") {
return (
parseHexColor(normalizedColor) ||
parseRgbColor(normalizedColor) || {
colorCssValue: normalizedColor,
alpha: 1
}
);
}
if (typeof normalizedColor === "object" && normalizedColor.rgb) {
return getNormalizedColorDescriptor(normalizedColor.rgb);
}
if (typeof normalizedColor === "object" && typeof normalizedColor.hex === "string") {
return getNormalizedColorDescriptor(normalizedColor.hex);
}
if (
typeof normalizedColor === "object" &&
normalizedColor.r !== undefined &&
normalizedColor.g !== undefined &&
normalizedColor.b !== undefined
) {
return {
colorCssValue: `rgb(${normalizedColor.r}, ${normalizedColor.g}, ${normalizedColor.b})`,
alpha: clampAlpha(normalizedColor.a)
};
}
return null;
};
/**
* Generates CSS styles for tinting card surfaces based on a provided color input. The function normalizes the input
* color,
* @param color
* @returns {{surfaceBg: string, surfaceHeaderBg: string, surfaceBorderColor: string}|{}}
*/
export const getTintedCardSurfaceStyles = (color) => {
const normalizedColor = getNormalizedColorDescriptor(color);
if (!normalizedColor?.colorCssValue) return {};
const tintStrength = clampAlpha(normalizedColor.alpha);
if (tintStrength === 0) return {};
const backgroundTint = roundTintPercentage(10 * tintStrength);
const headerTint = roundTintPercentage(18 * tintStrength);
const borderTint = roundTintPercentage(30 * tintStrength);
return {
surfaceBg: `color-mix(in srgb, ${normalizedColor.colorCssValue} ${backgroundTint}%, var(--imex-form-surface))`,
surfaceHeaderBg: `color-mix(in srgb, ${normalizedColor.colorCssValue} ${headerTint}%, var(--imex-form-surface-head))`,
surfaceBorderColor: `color-mix(in srgb, ${normalizedColor.colorCssValue} ${borderTint}%, var(--imex-form-surface-border))`
};
};

View File

@@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import { getTintedCardSurfaceStyles } from "./shop-info.color.utils";
describe("shop info color utilities", () => {
it("scales card tint intensity with alpha for plain rgba values", () => {
expect(
getTintedCardSurfaceStyles({
r: 22,
g: 119,
b: 255,
a: 0.5
})
).toEqual({
surfaceBg: "color-mix(in srgb, rgb(22, 119, 255) 5%, var(--imex-form-surface))",
surfaceHeaderBg: "color-mix(in srgb, rgb(22, 119, 255) 9%, var(--imex-form-surface-head))",
surfaceBorderColor: "color-mix(in srgb, rgb(22, 119, 255) 15%, var(--imex-form-surface-border))"
});
});
it("returns no tint when the selected color alpha is zero", () => {
expect(
getTintedCardSurfaceStyles({
hex: "#1677ff",
rgb: {
r: 22,
g: 119,
b: 255,
a: 0
}
})
).toEqual({});
});
it("supports legacy JSON-stringified picker values", () => {
expect(
getTintedCardSurfaceStyles(
JSON.stringify({
rgb: {
r: 255,
g: 0,
b: 0,
a: 0.25
}
})
)
).toEqual({
surfaceBg: "color-mix(in srgb, rgb(255, 0, 0) 2.5%, var(--imex-form-surface))",
surfaceHeaderBg: "color-mix(in srgb, rgb(255, 0, 0) 4.5%, var(--imex-form-surface-head))",
surfaceBorderColor: "color-mix(in srgb, rgb(255, 0, 0) 7.5%, var(--imex-form-surface-border))"
});
});
});

View File

@@ -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 { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const timeZonesList = Intl.supportedValuesOf("timeZone");
@@ -19,6 +20,16 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral);
export function ShopInfoGeneral({ form }) {
const { t } = useTranslation();
const messagingPresets = Form.useWatch(["md_messaging_presets"], form) || [];
const notesPresets = Form.useWatch(["md_notes_presets"], form) || [];
const partsLocations = Form.useWatch(["md_parts_locations"], form) || [];
const insuranceCompanies = Form.useWatch(["md_ins_cos"], form) || [];
const estimators = Form.useWatch(["md_estimators"], form) || [];
const fileHandlers = Form.useWatch(["md_filehandlers"], form) || [];
const courtesyCarRates = Form.useWatch(["md_ccc_rates"], form) || [];
const joblinePresets = Form.useWatch(["md_jobline_presets"], form) || [];
const partOrderComments = Form.useWatch(["md_parts_order_comment"], form) || [];
const notificationEmails = Form.useWatch(["md_to_emails"], form) || [];
return (
<div>
@@ -88,7 +99,7 @@ export function ShopInfoGeneral({ form }) {
name="phone"
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "phone")]}
>
<PhoneFormItem />
<PhoneFormItem formatDisplayOnly showPhoneAction />
</Form.Item>
<Form.Item label={t("bodyshop.fields.website")} name="website">
<Input />
@@ -445,9 +456,37 @@ export function ShopInfoGeneral({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const messagingPreset = messagingPresets[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("bodyshop.fields.messaginglabel"),
index,
messagingPreset.label,
messagingPreset.text
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("bodyshop.fields.messaginglabel")}
key={`${index}label`}
@@ -474,17 +513,10 @@ export function ShopInfoGeneral({ form }) {
>
<Input.TextArea autoSize />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -506,9 +538,37 @@ export function ShopInfoGeneral({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const notesPreset = notesPresets[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("bodyshop.fields.noteslabel"),
index,
notesPreset.label,
notesPreset.text
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("bodyshop.fields.noteslabel")}
key={`${index}label`}
@@ -535,17 +595,10 @@ export function ShopInfoGeneral({ form }) {
>
<Input.TextArea autoSize />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -567,9 +620,32 @@ export function ShopInfoGeneral({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const partsLocation = partsLocations[field.name];
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(t("bodyshop.fields.partslocation"), index, partsLocation)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
className="imex-flex-row__margin"
label={t("bodyshop.fields.partslocation")}
@@ -584,18 +660,10 @@ export function ShopInfoGeneral({ form }) {
>
<Input />
</Form.Item>
<Space wrap>
<DeleteFilled
className="imex-flex-row__margin"
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -623,9 +691,32 @@ export function ShopInfoGeneral({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const insuranceCompany = insuranceCompanies[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(t("bodyshop.fields.md_ins_co.name"), index, insuranceCompany.name)}
extra={
<Space align="center" size="small">
<Button
type="text"
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.name")}
key={`${index}name`}
@@ -702,18 +793,10 @@ export function ShopInfoGeneral({ form }) {
>
<Switch />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -738,9 +821,37 @@ export function ShopInfoGeneral({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const estimator = estimators[field.name] || {};
const estimatorName = [estimator.est_ct_fn, estimator.est_ct_ln].filter(Boolean).join(" ");
const estimatorTitle =
estimator.est_co_nm && estimatorName
? `${estimator.est_co_nm} | ${estimatorName}`
: getFormListItemTitle(t("jobs.fields.est_co_nm"), index, estimator.est_co_nm, estimatorName);
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={estimatorTitle}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("jobs.fields.est_co_nm")}
key={`${index}est_co_nm`}
@@ -770,7 +881,7 @@ export function ShopInfoGeneral({ form }) {
({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, [field.name, "est_ph"])
]}
>
<Input />
<PhoneFormItem formatDisplayOnly showPhoneAction />
</Form.Item>
<Form.Item
label={t("jobs.fields.est_ea")}
@@ -785,17 +896,10 @@ export function ShopInfoGeneral({ form }) {
>
<FormItemEmail email={form.getFieldValue([field.name, "est_ea"])} />
</Form.Item>
<Space>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -817,9 +921,38 @@ export function ShopInfoGeneral({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const fileHandler = fileHandlers[field.name] || {};
const fileHandlerName = [fileHandler.ins_ct_fn, fileHandler.ins_ct_ln].filter(Boolean).join(" ");
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("jobs.fields.ins_ct_fn"),
index,
fileHandlerName,
fileHandler.ins_ea
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("jobs.fields.ins_ct_fn")}
key={`${index}ins_ct_fn`}
@@ -842,7 +975,7 @@ export function ShopInfoGeneral({ form }) {
({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, [field.name, "ins_ph"])
]}
>
<Input />
<PhoneFormItem formatDisplayOnly showPhoneAction />
</Form.Item>
<Form.Item
label={t("jobs.fields.ins_ea")}
@@ -857,17 +990,10 @@ export function ShopInfoGeneral({ form }) {
>
<FormItemEmail email={form.getFieldValue([field.name, "ins_ea"])} />
</Form.Item>
<Space>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -890,9 +1016,32 @@ export function ShopInfoGeneral({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const courtesyCarRate = courtesyCarRates[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(t("general.labels.label"), index, courtesyCarRate.label)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
@@ -976,18 +1125,10 @@ export function ShopInfoGeneral({ form }) {
>
<InputNumber precision={2} />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -1011,9 +1152,37 @@ export function ShopInfoGeneral({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const joblinePreset = joblinePresets[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("general.labels.label"),
index,
joblinePreset.label,
joblinePreset.line_desc
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
@@ -1122,17 +1291,10 @@ export function ShopInfoGeneral({ form }) {
>
<Switch />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -1154,9 +1316,37 @@ export function ShopInfoGeneral({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const partOrderComment = partOrderComments[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("parts_orders.fields.comments"),
index,
partOrderComment.label,
partOrderComment.comment
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
@@ -1183,18 +1373,10 @@ export function ShopInfoGeneral({ form }) {
>
<Input.TextArea autoSize />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -1216,9 +1398,37 @@ export function ShopInfoGeneral({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const notificationEmail = notificationEmails[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("general.labels.label"),
index,
notificationEmail.label,
notificationEmail.emails
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
@@ -1245,18 +1455,10 @@ export function ShopInfoGeneral({ form }) {
>
<FormItemEmail email={form.getFieldValue([field.name, "emails"])} />
</Form.Item>
<Space>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"

View File

@@ -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 { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const SelectorDiv = styled.div`
@@ -17,6 +18,8 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
const { t } = useTranslation();
const TemplateListGenerated = TemplateList();
const intakeChecklistItems = Form.useWatch(["intakechecklist", "form"], form) || [];
const deliverChecklistItems = Form.useWatch(["deliverchecklist", "form"], form) || [];
return (
<div>
<LayoutFormRow header={t("bodyshop.labels.intakechecklist")} id="intakechecklist">
@@ -24,9 +27,37 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const intakeChecklistItem = intakeChecklistItems[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("jobs.fields.intake.name"),
index,
intakeChecklistItem.name,
intakeChecklistItem.label
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("jobs.fields.intake.name")}
key={`${index}name`}
@@ -69,7 +100,8 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
<Form.Item shouldUpdate>
{() => {
if (form.getFieldValue(["intakechecklist", "form", index, "type"]) !== "slider") return null;
if (form.getFieldValue(["intakechecklist", "form", index, "type"]) !== "slider")
return null;
return (
<>
<Form.Item
@@ -111,17 +143,10 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
>
<Switch />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -171,9 +196,37 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const deliverChecklistItem = deliverChecklistItems[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("jobs.fields.intake.name"),
index,
deliverChecklistItem.name,
deliverChecklistItem.label
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("jobs.fields.intake.name")}
key={`${index}named`}
@@ -218,7 +271,8 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
<Form.Item shouldUpdate>
{() => {
if (form.getFieldValue(["deliverchecklist", "form", index, "type"]) !== "slider") return null;
if (form.getFieldValue(["deliverchecklist", "form", index, "type"]) !== "slider")
return null;
return (
<>
<Form.Item
@@ -261,15 +315,10 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
>
<Switch />
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"

View File

@@ -3,10 +3,13 @@ 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 { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function ShopInfoLaborRates() {
const { t } = useTranslation();
const form = Form.useFormInstance();
const laborRates = Form.useWatch(["md_labor_rates"], form) || [];
return (
<>
@@ -23,9 +26,32 @@ export default function ShopInfoLaborRates() {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const laborRate = laborRates[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider={index === 0}>
<LayoutFormRow
noDivider={index === 0}
title={getFormListItemTitle(t("jobs.fields.labor_rate_desc"), index, laborRate.rate_label)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("jobs.fields.labor_rate_desc")}
key={`${index}rate_label`}
@@ -314,17 +340,10 @@ export default function ShopInfoLaborRates() {
>
<CurrencyInput min={0} />
</Form.Item>
<Space>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.rate_length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"

View File

@@ -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 { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import i18n from "i18next";
@@ -75,9 +76,41 @@ export default function ShopInfoPartsScan({ form }) {
{fields.map((field, index) => {
const selectedField = watchedFields?.[index]?.field || "line_desc";
const fieldType = getFieldType(selectedField);
const selectedFieldLabel =
fieldSelectOptions.find((option) => option.value === selectedField)?.label ||
t("bodyshop.fields.md_parts_scan.field");
const ruleTitle = watchedFields?.[index]?.value
? `${selectedFieldLabel}: ${watchedFields[index].value}`
: null;
return (
<Form.Item key={field.key}>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("bodyshop.fields.md_parts_scan.field"),
index,
ruleTitle,
selectedFieldLabel
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Row gutter={[16, 16]} align="middle">
{/* Select Field */}
<Col span={6}>
@@ -224,15 +257,8 @@ export default function ShopInfoPartsScan({ form }) {
<Input />
</Form.Item>
</Col>
{/* Actions */}
<Col span={2}>
<Space>
<DeleteFilled onClick={() => remove(field.name)} />
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</Col>
</Row>
</LayoutFormRow>
</Form.Item>
);
})}

View File

@@ -11,6 +11,7 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
import ShopInfoResponsibilitycentersTaxesComponent from "./shop-info.responsibilitycenters.taxes.component";
@@ -32,6 +33,9 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoResponsibili
export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
const { t } = useTranslation();
const dmsPayers = Form.useWatch(["cdk_configuration", "payers"], form) || [];
const costResponsibilityCenters = Form.useWatch(["md_responsibility_centers", "costs"], form) || [];
const profitResponsibilityCenters = Form.useWatch(["md_responsibility_centers", "profits"], form) || [];
const hasDMSKey = bodyshopHasDmsKey(bodyshop);
@@ -66,11 +70,14 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
};
const ReceivableCustomFieldSelect = (
<Select allowClear options={[
<Select
allowClear
options={[
{ value: "v_vin", label: "VIN" },
{ value: "clm_no", label: "Claim No." },
{ value: "ded_amt", label: "Deductible Amount" }
]} />
]}
/>
);
return (
@@ -494,9 +501,37 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const dmsPayer = dmsPayers[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("jobs.fields.dms.payer.name"),
index,
dmsPayer.name,
dmsPayer.dms_acctnumber
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("jobs.fields.dms.payer.name")}
key={`${index}name`}
@@ -518,24 +553,24 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}control_type`}
name={[field.name, "control_type"]}
>
<Select allowClear showSearch options={[
<Select
allowClear
showSearch
options={[
{ value: "ro_number", label: t("jobs.fields.ro_number") },
{ value: "clm_no", label: t("jobs.fields.clm_no") },
{ value: "po_number", label: t("jobs.fields.ponumber") },
{ value: "account_number", label: t("jobs.fields.dms.control_type.account_number") }
]} />
</Form.Item>
<Space align="center">
<DeleteFilled
onClick={() => {
remove(field.name);
}}
{
value: "account_number",
label: t("jobs.fields.dms.control_type.account_number")
}
]}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</Form.Item>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -611,9 +646,37 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const responsibilityCenter = costResponsibilityCenters[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("bodyshop.fields.responsibilitycenter"),
index,
responsibilityCenter.name,
responsibilityCenter.accountname
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter")}
key={`${index}name`}
@@ -673,18 +736,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input onBlur={handleBlur} />
</Form.Item>
)}
<Space align="center">
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -706,9 +761,37 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const responsibilityCenter = profitResponsibilityCenters[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("bodyshop.fields.responsibilitycenter"),
index,
responsibilityCenter.name,
responsibilityCenter.accountdesc
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter")}
key={`${index}name`}
@@ -771,11 +854,19 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
name={[field.name, "rr_item_type"]}
rules={[{ required: true }]}
>
<Select options={[
<Select
options={[
{ value: "G", label: t("bodyshop.fields.responsibilitycenters.item_type_gog") },
{ value: "P", label: t("bodyshop.fields.responsibilitycenters.item_type_paint") },
{ value: "F", label: t("bodyshop.fields.responsibilitycenters.item_type_freight") }
]} />
{
value: "P",
label: t("bodyshop.fields.responsibilitycenters.item_type_paint")
},
{
value: "F",
label: t("bodyshop.fields.responsibilitycenters.item_type_freight")
}
]}
/>
</Form.Item>
<Form.Item
@@ -784,24 +875,19 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
name={[field.name, "rr_cust_txbl_flag"]}
rules={[{ required: true }]}
>
<Select options={[
<Select
options={[
{ value: "T", label: t("bodyshop.fields.responsibilitycenters.taxable") },
{ value: "N", label: t("bodyshop.fields.responsibilitycenters.nontaxable") }
]} />
]}
/>
</Form.Item>
</>
)}
<Space align="center">
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
@@ -866,7 +952,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-ats`}
name={[field.name, "costs", "ATS"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.laa")}
@@ -884,7 +973,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LAA`}
name={[field.name, "costs", "LAA"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lab")}
@@ -902,7 +994,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LAB`}
name={[field.name, "costs", "LAB"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lad")}
@@ -920,7 +1015,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LAD`}
name={[field.name, "costs", "LAD"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lae")}
@@ -938,7 +1036,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LAE`}
name={[field.name, "costs", "LAE"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.laf")}
@@ -956,7 +1057,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LAF`}
name={[field.name, "costs", "LAF"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lag")}
@@ -974,7 +1078,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LAG`}
name={[field.name, "costs", "LAG"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lam")}
@@ -992,7 +1099,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LAM`}
name={[field.name, "costs", "LAM"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lar")}
@@ -1010,7 +1120,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LAR`}
name={[field.name, "costs", "LAR"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.las")}
@@ -1028,7 +1141,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LAS`}
name={[field.name, "costs", "LAS"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lau")}
@@ -1046,7 +1162,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LAU`}
name={[field.name, "costs", "LAU"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.la1")}
@@ -1064,7 +1183,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LA1`}
name={[field.name, "costs", "LA1"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.la2")}
@@ -1082,7 +1204,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LA2`}
name={[field.name, "costs", "LA2"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.la3")}
@@ -1100,7 +1225,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LA3`}
name={[field.name, "costs", "LA3"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.la4")}
@@ -1118,7 +1246,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-LA4`}
name={[field.name, "costs", "LA4"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.paa")}
@@ -1136,7 +1267,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PAA`}
name={[field.name, "costs", "PAA"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pac")}
@@ -1154,7 +1288,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PAC`}
name={[field.name, "costs", "PAC"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pag")}
@@ -1172,7 +1309,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PAG`}
name={[field.name, "costs", "PAG"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pal")}
@@ -1190,7 +1330,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PAL`}
name={[field.name, "costs", "PAL"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pam")}
@@ -1208,7 +1351,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PAM`}
name={[field.name, "costs", "PAM"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pan")}
@@ -1226,7 +1372,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PAN`}
name={[field.name, "costs", "PAN"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pao")}
@@ -1244,7 +1393,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PAO`}
name={[field.name, "costs", "PAO"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pap")}
@@ -1262,7 +1414,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PAP`}
name={[field.name, "costs", "PAP"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.par")}
@@ -1280,7 +1435,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PAR`}
name={[field.name, "costs", "PAR"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pas")}
@@ -1298,7 +1456,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PAS`}
name={[field.name, "costs", "PAS"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pasl")}
@@ -1316,7 +1477,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-PASL`}
name={[field.name, "costs", "PASL"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.tow")}
@@ -1334,7 +1498,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-TOW`}
name={[field.name, "costs", "TOW"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.mapa")}
@@ -1352,7 +1519,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-MAPA`}
name={[field.name, "costs", "MAPA"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.mash")}
@@ -1370,7 +1540,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}costs-MASH`}
name={[field.name, "costs", "MASH"]}
>
<Select showSearch options={costOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={costOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.dms.profitsmapping")} id="profitsmapping">
@@ -1390,7 +1563,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-ATS`}
name={[field.name, "profits", "ATS"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.laa")}
@@ -1408,7 +1584,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LAA`}
name={[field.name, "profits", "LAA"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lab")}
@@ -1426,7 +1605,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LAB`}
name={[field.name, "profits", "LAB"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lad")}
@@ -1444,7 +1626,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LAD`}
name={[field.name, "profits", "LAD"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lae")}
@@ -1462,7 +1647,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LAE`}
name={[field.name, "profits", "LAE"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.laf")}
@@ -1480,7 +1668,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LAF`}
name={[field.name, "profits", "LAF"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lag")}
@@ -1498,7 +1689,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LAG`}
name={[field.name, "profits", "LAG"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lam")}
@@ -1516,7 +1710,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LAM`}
name={[field.name, "profits", "LAM"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lar")}
@@ -1534,7 +1731,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LAR`}
name={[field.name, "profits", "LAR"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.las")}
@@ -1552,7 +1752,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LAS`}
name={[field.name, "profits", "LAS"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.lau")}
@@ -1570,7 +1773,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LAU`}
name={[field.name, "profits", "LAU"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.la1")}
@@ -1588,7 +1794,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LA1`}
name={[field.name, "profits", "LA1"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.la2")}
@@ -1606,7 +1815,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LA2`}
name={[field.name, "profits", "LA2"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.la3")}
@@ -1624,7 +1836,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LA3`}
name={[field.name, "profits", "LA3"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.la4")}
@@ -1642,7 +1857,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-LA4`}
name={[field.name, "profits", "LA4"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.paa")}
@@ -1660,7 +1878,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PAA`}
name={[field.name, "profits", "PAA"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pac")}
@@ -1678,7 +1899,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PAC`}
name={[field.name, "profits", "PAC"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pag")}
@@ -1696,7 +1920,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PAG`}
name={[field.name, "profits", "PAG"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pal")}
@@ -1714,7 +1941,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PAL`}
name={[field.name, "profits", "PAL"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pam")}
@@ -1732,7 +1962,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PAM`}
name={[field.name, "profits", "PAM"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pan")}
@@ -1750,7 +1983,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PAN`}
name={[field.name, "profits", "PAN"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pao")}
@@ -1768,7 +2004,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PAO`}
name={[field.name, "profits", "PAO"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pap")}
@@ -1786,7 +2025,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PAP`}
name={[field.name, "profits", "PAP"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.par")}
@@ -1804,7 +2046,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PAR`}
name={[field.name, "profits", "PAR"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pas")}
@@ -1822,7 +2067,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PAS`}
name={[field.name, "profits", "PAS"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.pasl")}
@@ -1840,7 +2088,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-PASL`}
name={[field.name, "profits", "PASL"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.tow")}
@@ -1858,7 +2109,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-TOW`}
name={[field.name, "profits", "TOW"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.mapa")}
@@ -1876,7 +2130,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-MAPA`}
name={[field.name, "profits", "MAPA"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.mash")}
@@ -1894,7 +2151,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
key={`${index}profits-MASH`}
name={[field.name, "profits", "MASH"]}
>
<Select showSearch options={profitOptions.map((item) => ({ value: item, label: item }))} />
<Select
showSearch
options={profitOptions.map((item) => ({ value: item, label: item }))}
/>
</Form.Item>
</LayoutFormRow>
</div>
@@ -2950,6 +3210,53 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
>
<InputNumber precision={2} />
</Form.Item>
{InstanceRenderManager({
imex: [
<Form.Item
key="state_tax_name"
label={t("bodyshop.fields.responsibilitycenters.state_tax")}
rules={[{ required: true }]}
name={["md_responsibility_centers", "taxes", "state", "name"]}
>
<Input />
</Form.Item>,
<Form.Item
key="state_tax_accountdesc"
label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
rules={[{ required: true }]}
name={["md_responsibility_centers", "taxes", "state", "accountdesc"]}
>
<Input />
</Form.Item>,
<Form.Item
key="state_tax_accountitem"
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
rules={[{ required: true }]}
name={["md_responsibility_centers", "taxes", "state", "accountitem"]}
>
<Input />
</Form.Item>,
hasDMSKey ? (
<Form.Item
key="state_tax_dms_acctnumber"
label={t("bodyshop.fields.dms.dms_acctnumber")}
rules={[{ required: true }]}
name={["md_responsibility_centers", "taxes", "state", "dms_acctnumber"]}
>
<Input />
</Form.Item>
) : null,
<Form.Item
key="state_tax_rate"
label={t("bodyshop.fields.responsibilitycenter_rate")}
rules={[{ required: true }]}
name={["md_responsibility_centers", "taxes", "state", "rate"]}
>
<InputNumber precision={2} />
</Form.Item>
],
rome: null
})}
</LayoutFormRow>
{DmsAp.treatment === "on" && (
@@ -2994,48 +3301,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</LayoutFormRow>
)}
{InstanceRenderManager({
imex: (
<LayoutFormRow id="state_tax">
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.state_tax")}
rules={[{ required: true }]}
name={["md_responsibility_centers", "taxes", "state", "name"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
rules={[{ required: true }]}
name={["md_responsibility_centers", "taxes", "state", "accountdesc"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
rules={[{ required: true }]}
name={["md_responsibility_centers", "taxes", "state", "accountitem"]}
>
<Input />
</Form.Item>
{hasDMSKey && (
<Form.Item
label={t("bodyshop.fields.dms.dms_acctnumber")}
rules={[{ required: true }]}
name={["md_responsibility_centers", "taxes", "state", "dms_acctnumber"]}
>
<Input />
</Form.Item>
)}
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_rate")}
rules={[{ required: true }]}
name={["md_responsibility_centers", "taxes", "state", "rate"]}
>
<InputNumber precision={2} />
</Form.Item>
</LayoutFormRow>
),
imex: null,
rome: <ShopInfoResponsibilitycentersTaxesComponent form={form} />
})}
{HasFeatureAccess({ featureName: "export", bodyshop }) && (

View File

@@ -1,10 +1,11 @@
import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Select, Space } from "antd";
import { useState } from "react";
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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { DEFAULT_TRANSLUCENT_CARD_COLOR, getTintedCardSurfaceStyles } from "./shop-info.color.utils";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -28,6 +29,11 @@ const SelectorDiv = styled.div`
export function ShopInfoROStatusComponent({ bodyshop, form }) {
const { t } = useTranslation();
const statusOptions = Form.useWatch(["md_ro_statuses", "statuses"], form) || [];
const productionStatuses = Form.useWatch(["md_ro_statuses", "production_statuses"], form) || [];
const additionalBoardStatuses = Form.useWatch(["md_ro_statuses", "additional_board_statuses"], form) || [];
const productionColors = Form.useWatch(["md_ro_statuses", "production_colors"], form) || [];
const availableProductionStatuses = [...new Set([...productionStatuses, ...additionalBoardStatuses].filter(Boolean))];
const {
treatments: { Production_List_Status_Colors }
@@ -37,23 +43,6 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
splitKey: bodyshop.imexshopid
});
const [options, setOptions] = useState(form.getFieldValue(["md_ro_statuses", "statuses"]) || []);
const [productionStatus, setProductionStatus] = useState(
(form.getFieldValue(["md_ro_statuses", "production_statuses"]) || []).concat(
form.getFieldValue(["md_ro_statuses", "additional_board_statuses"]) || []
) || []
);
const handleBlur = () => {
setOptions(form.getFieldValue(["md_ro_statuses", "statuses"]));
setProductionStatus(
form
.getFieldValue(["md_ro_statuses", "production_statuses"])
.concat(form.getFieldValue(["md_ro_statuses", "additional_board_statuses"]))
);
};
return (
<SelectorDiv id="jobstatus">
<Form.Item
@@ -67,7 +56,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
}
]}
>
<Select mode="tags" onBlur={handleBlur} />
<Select mode="tags" />
</Form.Item>
<Form.Item
name={["md_ro_statuses", "active_statuses"]}
@@ -80,7 +69,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
}
]}
>
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
name={["md_ro_statuses", "pre_production_statuses"]}
@@ -93,7 +82,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
}
]}
>
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
name={["md_ro_statuses", "production_statuses"]}
@@ -106,7 +95,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
}
]}
>
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
name={["md_ro_statuses", "post_production_statuses"]}
@@ -119,7 +108,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
}
]}
>
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
name={["md_ro_statuses", "ready_statuses"]}
@@ -132,7 +121,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
}
]}
>
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
name={["md_ro_statuses", "additional_board_statuses"]}
@@ -145,7 +134,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
}
]}
>
<Select mode="multiple" options={options.map((item) => ({ value: item, label: item }))} />
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<LayoutFormRow noDivider>
<Form.Item
@@ -158,7 +147,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
]}
name={["md_ro_statuses", "default_scheduled"]}
>
<Select options={options.map((item) => ({ value: item, label: item }))} />
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_arrived")}
@@ -170,7 +159,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
]}
name={["md_ro_statuses", "default_arrived"]}
>
<Select options={options.map((item) => ({ value: item, label: item }))} />
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_exported")}
@@ -182,7 +171,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
]}
name={["md_ro_statuses", "default_exported"]}
>
<Select options={options.map((item) => ({ value: item, label: item }))} />
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_imported")}
@@ -194,7 +183,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
]}
name={["md_ro_statuses", "default_imported"]}
>
<Select options={options.map((item) => ({ value: item, label: item }))} />
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_invoiced")}
@@ -206,7 +195,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
]}
name={["md_ro_statuses", "default_invoiced"]}
>
<Select options={options.map((item) => ({ value: item, label: item }))} />
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_completed")}
@@ -218,7 +207,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
]}
name={["md_ro_statuses", "default_completed"]}
>
<Select options={options.map((item) => ({ value: item, label: item }))} />
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_delivered")}
@@ -230,7 +219,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
]}
name={["md_ro_statuses", "default_delivered"]}
>
<Select options={options.map((item) => ({ value: item, label: item }))} />
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.default_void")}
@@ -242,7 +231,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
]}
name={["md_ro_statuses", "default_void"]}
>
<Select options={options.map((item) => ({ value: item, label: item }))} />
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
</Form.Item>
</LayoutFormRow>
{Production_List_Status_Colors.treatment === "on" && (
@@ -251,13 +240,41 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
{(fields, { add, remove }) => {
return (
<div>
<Space size="large" wrap>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Space orientation="vertical">
<div style={{ display: "flex" }}>
<Space size="large" wrap align="start">
{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 (
<LayoutFormRow
key={field.key}
noDivider
title={getFormListItemTitle(t("jobs.fields.status"), index, productionColor.status)}
extra={
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
}
{...productionColorSurfaceStyles}
style={{ width: 260, marginBottom: 0 }}
>
<div>
<Form.Item
style={{ flex: 1 }}
label={t("jobs.fields.status")}
key={`${index}status`}
name={[field.name, "status"]}
@@ -268,14 +285,10 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
}
]}
>
<Select options={productionStatus.map((item) => ({ value: item, label: item }))} />
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
<Select
options={productionColorStatusOptions.map((item) => ({ value: item, label: item }))}
/>
</div>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.statuses.color")}
key={`${index}color`}
@@ -289,15 +302,18 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
>
<ColorPicker />
</Form.Item>
</div>
</LayoutFormRow>
);
})}
</Space>
</Form.Item>
))}
</Space>
<Form.Item>
<Form.Item style={{ marginTop: 8 }}>
<Button
type="dashed"
onClick={() => {
add();
add({
color: { ...DEFAULT_TRANSLUCENT_CARD_COLOR }
});
}}
style={{ width: "100%" }}
>

View File

@@ -1,5 +1,5 @@
import { DeleteFilled } from "@ant-design/icons";
import { Button, Divider, Form, Input, InputNumber, Select, Space, Switch, TimePicker } from "antd";
import { Button, Form, Input, InputNumber, Select, Space, Switch, TimePicker } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -7,8 +7,14 @@ 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 { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { ColorPicker } from "./shop-info.rostatus.component";
import {
DEFAULT_TRANSLUCENT_CARD_COLOR,
DEFAULT_TRANSLUCENT_PICKER_COLOR,
getTintedCardSurfaceStyles
} from "./shop-info.color.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -17,12 +23,24 @@ const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
const WORKING_DAYS = [
{ key: "sunday", labelKey: "general.labels.sunday" },
{ key: "monday", labelKey: "general.labels.monday" },
{ key: "tuesday", labelKey: "general.labels.tuesday" },
{ key: "wednesday", labelKey: "general.labels.wednesday" },
{ key: "thursday", labelKey: "general.labels.thursday" },
{ key: "friday", labelKey: "general.labels.friday" },
{ key: "saturday", labelKey: "general.labels.saturday" }
];
export function ShopInfoSchedulingComponent({ form, bodyshop }) {
const { t } = useTranslation();
const appointmentColors = Form.useWatch(["appt_colors"], form) || form.getFieldValue(["appt_colors"]) || [];
const schedulingBuckets = Form.useWatch(["ssbuckets"], form) || form.getFieldValue(["ssbuckets"]) || [];
return (
<div>
<LayoutFormRow id="shopinfo-scheduling">
<LayoutFormRow grow header={t("bodyshop.labels.scheduling")} id="shopinfo-scheduling">
<Form.Item
label={t("bodyshop.fields.appt_length")}
name={"appt_length"}
@@ -100,38 +118,53 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
<Select mode="tags" />
</Form.Item>
</LayoutFormRow>
<Divider titlePlacement="left">{t("bodyshop.labels.workingdays")}</Divider>
<Space wrap size="large" id="workingdays">
<Form.Item label={t("general.labels.sunday")} name={["workingdays", "sunday"]} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item label={t("general.labels.monday")} name={["workingdays", "monday"]} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item label={t("general.labels.tuesday")} name={["workingdays", "tuesday"]} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item label={t("general.labels.wednesday")} name={["workingdays", "wednesday"]} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item label={t("general.labels.thursday")} name={["workingdays", "thursday"]} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item label={t("general.labels.friday")} name={["workingdays", "friday"]} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item label={t("general.labels.saturday")} name={["workingdays", "saturday"]} valuePropName="checked">
<LayoutFormRow header={t("bodyshop.labels.workingdays")} id="workingdays">
<Space wrap size="middle">
{WORKING_DAYS.map(({ key, labelKey }) => (
<Form.Item key={key} label={t(labelKey)} name={["workingdays", key]} valuePropName="checked">
<Switch />
</Form.Item>
))}
</Space>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.apptcolors")} id="apptcolors">
<Form.List name={["appt_colors"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const appointmentColor =
appointmentColors[field.name] || form.getFieldValue(["appt_colors", field.name]) || {};
const appointmentColorSurfaceStyles = getTintedCardSurfaceStyles(appointmentColor.color);
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("bodyshop.fields.appt_colors.label"),
index,
appointmentColor.label
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
{...appointmentColorSurfaceStyles}
>
<Form.Item
label={t("bodyshop.fields.appt_colors.label")}
key={`${index}aptcolorlabel`}
@@ -158,22 +191,20 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
>
<ColorpickerFormItemComponent />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
add({
color: {
...DEFAULT_TRANSLUCENT_PICKER_COLOR,
rgb: { ...DEFAULT_TRANSLUCENT_PICKER_COLOR.rgb }
}
});
}}
style={{ width: "100%" }}
>
@@ -191,9 +222,40 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const schedulingBucket =
schedulingBuckets[field.name] || form.getFieldValue(["ssbuckets", field.name]) || {};
const schedulingBucketSurfaceStyles = getTintedCardSurfaceStyles(schedulingBucket.color);
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("bodyshop.fields.ssbuckets.label"),
index,
schedulingBucket.label,
schedulingBucket.id
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
{...schedulingBucketSurfaceStyles}
>
<Form.Item
label={t("bodyshop.fields.ssbuckets.id")}
key={`${index}id`}
@@ -257,7 +319,6 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
<InputNumber />
</Form.Item>
<Space orientation="horizontal">
<Form.Item
label={
<Space>
@@ -284,23 +345,17 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
>
<ColorPicker />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
add({
color: { ...DEFAULT_TRANSLUCENT_CARD_COLOR }
});
}}
style={{ width: "100%" }}
>

View File

@@ -3,12 +3,15 @@ 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 { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
export default function ShopInfoSpeedPrint() {
const { t } = useTranslation();
const form = Form.useFormInstance();
const allTemplates = TemplateList("job");
const speedPrintItems = Form.useWatch(["speedprint"], form) || [];
const TemplateListGenerated = InstanceRenderManager({
imex: Object.fromEntries(Object.entries(allTemplates).filter(([, { enhanced_payroll }]) => !enhanced_payroll)),
rome: allTemplates
@@ -19,9 +22,32 @@ export default function ShopInfoSpeedPrint() {
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const speedPrintItem = speedPrintItems[field.name] || {};
return (
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
<LayoutFormRow grow>
<LayoutFormRow
grow
title={getFormListItemTitle(
t("bodyshop.fields.speedprint.label"),
index,
speedPrintItem.label,
speedPrintItem.id
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
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`}
@@ -68,18 +94,10 @@ export default function ShopInfoSpeedPrint() {
}))}
/>
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.Item>
<Button
type="dashed"

View File

@@ -2,6 +2,7 @@ import { DeleteFilled } from "@ant-design/icons";
import { Button, Checkbox, Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd";
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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { connect } from "react-redux";
@@ -55,6 +56,8 @@ const getTaskPresetAllocationErrors = (presets = [], t) => {
export function ShopInfoTaskPresets({ bodyshop }) {
const { t } = useTranslation();
const form = Form.useFormInstance();
const taskPresets = Form.useWatch(["md_tasks_presets", "presets"], form) || [];
return (
<>
@@ -93,9 +96,37 @@ export function ShopInfoTaskPresets({ bodyshop }) {
{(fields, { add, remove, move }, { errors }) => {
return (
<div>
{fields.map((field, index) => (
{fields.map((field, index) => {
const taskPreset = taskPresets[field.name] || {};
return (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("bodyshop.fields.md_tasks_presets.name"),
index,
taskPreset.name,
taskPreset.memo
)}
extra={
<Space align="center" size="small">
<Button
type="text"
icon={<DeleteFilled />}
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
>
<Form.Item
label={t("bodyshop.fields.md_tasks_presets.name")}
key={`${index}name`}
@@ -228,17 +259,10 @@ export function ShopInfoTaskPresets({ bodyshop }) {
}))}
/>
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
);
})}
<Form.ErrorList errors={errors} />
<Form.Item>
<Button