IO-3624 Refresh shop config list rows and color UX
This commit is contained in:
@@ -17,7 +17,7 @@ export default function FormsFieldChanged({ form, skipPrompt }) {
|
|||||||
const errors = form.getFieldsError().filter((e) => e.errors.length > 0);
|
const errors = form.getFieldsError().filter((e) => e.errors.length > 0);
|
||||||
if (form.isFieldsTouched())
|
if (form.isFieldsTouched())
|
||||||
return (
|
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")} />
|
<Prompt when={!skipPrompt} beforeUnload={true} message={t("general.messages.unsavedchangespopup")} />
|
||||||
<AlertComponent
|
<AlertComponent
|
||||||
type="warning"
|
type="warning"
|
||||||
|
|||||||
@@ -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 i18n from "i18next";
|
||||||
import parsePhoneNumber from "libphonenumber-js";
|
import parsePhoneNumber from "libphonenumber-js";
|
||||||
|
import { forwardRef, useMemo, useState } from "react";
|
||||||
import "./phone-form-item.styles.scss";
|
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;
|
export default FormItemPhone;
|
||||||
|
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
}
|
||||||
@@ -7,32 +7,37 @@ export default function LayoutFormRow({
|
|||||||
children,
|
children,
|
||||||
grow = false,
|
grow = false,
|
||||||
noDivider = false,
|
noDivider = false,
|
||||||
gutter = [16, 16], // Responsive gutter: horizontal, vertical
|
gutter,
|
||||||
rowProps,
|
rowProps,
|
||||||
|
|
||||||
// Optional overrides if you ever need per-section customization
|
// Optional overrides if you ever need per-section customization
|
||||||
surface = true,
|
surface = true,
|
||||||
surfaceBg,
|
surfaceBg,
|
||||||
surfaceHeaderBg,
|
surfaceHeaderBg,
|
||||||
|
surfaceBorderColor,
|
||||||
|
|
||||||
...cardProps
|
...cardProps
|
||||||
}) {
|
}) {
|
||||||
const items = Children.toArray(children).filter(Boolean);
|
const items = Children.toArray(children).filter(Boolean);
|
||||||
if (items.length === 0) return null;
|
if (items.length === 0) return null;
|
||||||
|
const isCompactRow = noDivider;
|
||||||
|
|
||||||
const title = !noDivider && header ? header : undefined;
|
const title = !noDivider && header ? header : undefined;
|
||||||
|
const resolvedGutter = gutter ?? [16, isCompactRow ? 8 : 16];
|
||||||
|
|
||||||
const bg = surfaceBg ?? (surface ? "var(--imex-form-surface)" : undefined);
|
const bg = surfaceBg ?? (surface ? "var(--imex-form-surface)" : undefined);
|
||||||
const headBg = surfaceHeaderBg ?? (surface ? "var(--imex-form-surface-head)" : undefined);
|
const headBg = surfaceHeaderBg ?? (surface ? "var(--imex-form-surface-head)" : undefined);
|
||||||
|
const borderColor = surfaceBorderColor ?? (surface ? "var(--imex-form-surface-border)" : undefined);
|
||||||
|
|
||||||
const mergedStyles = mergeSemanticStyles(
|
const mergedStyles = mergeSemanticStyles(
|
||||||
{
|
{
|
||||||
header: {
|
header: {
|
||||||
paddingInline: 16,
|
paddingInline: isCompactRow ? 12 : 16,
|
||||||
background: headBg
|
background: headBg,
|
||||||
|
borderBottomColor: borderColor
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
padding: 16,
|
padding: isCompactRow ? 12 : 16,
|
||||||
background: bg
|
background: bg
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -40,8 +45,9 @@ export default function LayoutFormRow({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const baseCardStyle = {
|
const baseCardStyle = {
|
||||||
marginBottom: ".8rem",
|
marginBottom: isCompactRow ? "8px" : ".8rem",
|
||||||
...(bg ? { background: bg } : null), // ensures the “circled area” is tinted
|
...(bg ? { background: bg } : null), // ensures the “circled area” is tinted
|
||||||
|
...(borderColor ? { borderColor } : null),
|
||||||
...cardProps.style
|
...cardProps.style
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,7 +59,9 @@ export default function LayoutFormRow({
|
|||||||
title={cardProps.title ?? title}
|
title={cardProps.title ?? title}
|
||||||
size={cardProps.size ?? "small"}
|
size={cardProps.size ?? "small"}
|
||||||
variant={cardProps.variant ?? "outlined"}
|
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}
|
style={baseCardStyle}
|
||||||
styles={mergedStyles}
|
styles={mergedStyles}
|
||||||
>
|
>
|
||||||
@@ -128,11 +136,13 @@ export default function LayoutFormRow({
|
|||||||
title={cardProps.title ?? title}
|
title={cardProps.title ?? title}
|
||||||
size={cardProps.size ?? "small"}
|
size={cardProps.size ?? "small"}
|
||||||
variant={cardProps.variant ?? "outlined"}
|
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}
|
style={baseCardStyle}
|
||||||
styles={mergedStyles}
|
styles={mergedStyles}
|
||||||
>
|
>
|
||||||
<Row gutter={gutter} wrap {...rowProps}>
|
<Row gutter={resolvedGutter} wrap {...rowProps}>
|
||||||
{items.map((child, idx) => (
|
{items.map((child, idx) => (
|
||||||
<Col key={child?.key ?? idx} {...getColPropsForChild(child)}>
|
<Col key={child?.key ?? idx} {...getColPropsForChild(child)}>
|
||||||
{child}
|
{child}
|
||||||
|
|||||||
@@ -43,10 +43,29 @@ html[data-theme="dark"] {
|
|||||||
border-bottom-color: var(--imex-form-surface-border);
|
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 {
|
.ant-card-body {
|
||||||
background: var(--imex-form-surface);
|
background: var(--imex-form-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-form-item:last-child {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Optional: tighter spacing on phones for better space usage */
|
/* Optional: tighter spacing on phones for better space usage */
|
||||||
@media (max-width: 575px) {
|
@media (max-width: 575px) {
|
||||||
.ant-card-head {
|
.ant-card-head {
|
||||||
@@ -70,6 +89,10 @@ html[data-theme="dark"] {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-form-item:has(> .imex-form-row--compact) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Better form item spacing on mobile */
|
/* Better form item spacing on mobile */
|
||||||
@media (max-width: 575px) {
|
@media (max-width: 575px) {
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { DeleteFilled, DownOutlined, WarningFilled } from "@ant-design/icons";
|
import { DeleteFilled, DownOutlined, WarningFilled } from "@ant-design/icons";
|
||||||
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
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 { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
|
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
|
||||||
@@ -50,6 +51,7 @@ export function PartsOrderModalComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const partsOrderLines = Form.useWatch(["parts_order_lines", "data"], form) || [];
|
||||||
const handleClick = ({ item }) => {
|
const handleClick = ({ item }) => {
|
||||||
form.setFieldsValue({ comments: item.props.value });
|
form.setFieldsValue({ comments: item.props.value });
|
||||||
};
|
};
|
||||||
@@ -128,10 +130,38 @@ export function PartsOrderModalComponent({
|
|||||||
{(fields, { remove, move }) => {
|
{(fields, { remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item required={false} key={field.key}>
|
const partsOrderLine = partsOrderLines[field.name] || {};
|
||||||
<div style={{ display: "flex" }}>
|
|
||||||
<LayoutFormRow grow noDivider style={{ flex: 1 }}>
|
return (
|
||||||
|
<Form.Item required={false} key={field.key}>
|
||||||
|
<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
|
<Form.Item
|
||||||
//span={8}
|
//span={8}
|
||||||
label={t("parts_orders.fields.line_desc")}
|
label={t("parts_orders.fields.line_desc")}
|
||||||
@@ -220,20 +250,9 @@ export function PartsOrderModalComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Space wrap size="small" align="center">
|
</Form.Item>
|
||||||
<div>
|
);
|
||||||
<DeleteFilled
|
})}
|
||||||
style={{ margin: "1rem" }}
|
|
||||||
onClick={() => {
|
|
||||||
remove(field.name);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
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 { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -15,6 +16,7 @@ export default connect(mapStateToProps, null)(PartsReceiveModalComponent);
|
|||||||
|
|
||||||
export function PartsReceiveModalComponent({ bodyshop, form }) {
|
export function PartsReceiveModalComponent({ bodyshop, form }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const partsOrderLines = Form.useWatch(["partsorderlines"], form) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -42,16 +44,43 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
|
|||||||
{(fields, { remove, move }) => {
|
{(fields, { remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item required={false} key={field.key}>
|
const partsOrderLine = partsOrderLines[field.name] || {};
|
||||||
<div style={{ display: "flex", alignItems: "center" }}>
|
|
||||||
|
return (
|
||||||
|
<Form.Item required={false} key={field.key}>
|
||||||
<Form.Item hidden key={`${index}joblineid`} name={[field.name, "joblineid"]}>
|
<Form.Item hidden key={`${index}joblineid`} name={[field.name, "joblineid"]}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item hidden key={`${index}id`} name={[field.name, "id"]}>
|
<Form.Item hidden key={`${index}id`} name={[field.name, "id"]}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</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
|
<Form.Item
|
||||||
label={t("parts_orders.fields.line_desc")}
|
label={t("parts_orders.fields.line_desc")}
|
||||||
key={`${index}line_desc`}
|
key={`${index}line_desc`}
|
||||||
@@ -101,16 +130,9 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
|
|||||||
<InputNumber min={0} />
|
<InputNumber min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<DeleteFilled
|
</Form.Item>
|
||||||
style={{ margin: "1rem" }}
|
);
|
||||||
onClick={() => {
|
})}
|
||||||
remove(field.name);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
|
||||||
</div>
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import { DeleteFilled } from "@ant-design/icons";
|
|||||||
import { Button, Form, Input, Select, Space } from "antd";
|
import { Button, Form, Input, Select, Space } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
export default function PartsEmailPresetsComponent() {
|
export default function PartsEmailPresetsComponent() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const emailPresets = Form.useWatch(["md_to_emails"], form) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -14,31 +17,46 @@ export default function PartsEmailPresetsComponent() {
|
|||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key}>
|
const preset = emailPresets[field.name] || {};
|
||||||
<LayoutFormRow noDivider>
|
|
||||||
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("bodyshop.labels.md_to_emails_emails")}
|
|
||||||
key={`${index}emails`}
|
|
||||||
name={[field.name, "emails"]}
|
|
||||||
>
|
|
||||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Space>
|
return (
|
||||||
<DeleteFilled
|
<Form.Item key={field.key}>
|
||||||
onClick={() => {
|
<LayoutFormRow
|
||||||
remove(field.name);
|
noDivider
|
||||||
}}
|
title={getFormListItemTitle(t("general.labels.label"), index, preset.label, preset.emails)}
|
||||||
/>
|
extra={
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
<Space align="center" size="small">
|
||||||
</Space>
|
<Button
|
||||||
</LayoutFormRow>
|
type="text"
|
||||||
</Form.Item>
|
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>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.labels.md_to_emails_emails")}
|
||||||
|
key={`${index}emails`}
|
||||||
|
name={[field.name, "emails"]}
|
||||||
|
>
|
||||||
|
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import { DeleteFilled } from "@ant-design/icons";
|
|||||||
import { Button, Form, Input, Space } from "antd";
|
import { Button, Form, Input, Space } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
export default function PartsLocationsComponent() {
|
export default function PartsLocationsComponent() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const partsLocations = Form.useWatch(["md_parts_locations"], form) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -14,34 +17,49 @@ export default function PartsLocationsComponent() {
|
|||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key}>
|
const location = partsLocations[field.name];
|
||||||
<LayoutFormRow noDivider>
|
|
||||||
<Form.Item
|
return (
|
||||||
className="imex-flex-row__margin"
|
<Form.Item key={field.key}>
|
||||||
label={t("bodyshop.fields.partslocation")}
|
<LayoutFormRow
|
||||||
key={`${index}`}
|
noDivider
|
||||||
name={[field.name]}
|
title={getFormListItemTitle(t("bodyshop.fields.partslocation"), index, location)}
|
||||||
rules={[
|
extra={
|
||||||
{
|
<Space align="center" size="small">
|
||||||
required: true
|
<Button
|
||||||
}
|
type="text"
|
||||||
]}
|
icon={<DeleteFilled />}
|
||||||
|
onClick={() => {
|
||||||
|
remove(field.name);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormListMoveArrows
|
||||||
|
move={move}
|
||||||
|
index={index}
|
||||||
|
total={fields.length}
|
||||||
|
orientation="horizontal"
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Input />
|
<Form.Item
|
||||||
</Form.Item>
|
|
||||||
<Space wrap>
|
|
||||||
<DeleteFilled
|
|
||||||
className="imex-flex-row__margin"
|
className="imex-flex-row__margin"
|
||||||
onClick={() => {
|
label={t("bodyshop.fields.partslocation")}
|
||||||
remove(field.name);
|
key={`${index}`}
|
||||||
}}
|
name={[field.name]}
|
||||||
/>
|
rules={[
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
{
|
||||||
</Space>
|
required: true
|
||||||
</LayoutFormRow>
|
}
|
||||||
</Form.Item>
|
]}
|
||||||
))}
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import { DeleteFilled } from "@ant-design/icons";
|
|||||||
import { Button, Form, Input, Space } from "antd";
|
import { Button, Form, Input, Space } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
export default function PartsOrderCommentsComponent() {
|
export default function PartsOrderCommentsComponent() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const orderComments = Form.useWatch(["md_parts_order_comment"], form) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -14,45 +17,65 @@ export default function PartsOrderCommentsComponent() {
|
|||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key}>
|
const comment = orderComments[field.name] || {};
|
||||||
<LayoutFormRow noDivider>
|
|
||||||
<Form.Item
|
|
||||||
label={t("general.labels.label")}
|
|
||||||
key={`${index}label`}
|
|
||||||
name={[field.name, "label"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("parts_orders.fields.comments")}
|
|
||||||
key={`${index}comment`}
|
|
||||||
name={[field.name, "comment"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input.TextArea autoSize />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Space wrap>
|
return (
|
||||||
<DeleteFilled
|
<Form.Item key={field.key}>
|
||||||
onClick={() => {
|
<LayoutFormRow
|
||||||
remove(field.name);
|
noDivider
|
||||||
}}
|
title={getFormListItemTitle(
|
||||||
/>
|
t("parts_orders.fields.comments"),
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
index,
|
||||||
</Space>
|
comment.label,
|
||||||
</LayoutFormRow>
|
comment.comment
|
||||||
</Form.Item>
|
)}
|
||||||
))}
|
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"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("parts_orders.fields.comments")}
|
||||||
|
key={`${index}comment`}
|
||||||
|
name={[field.name, "comment"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input.TextArea autoSize />
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { useApolloClient, useMutation, useQuery } from "@apollo/client/react";
|
import { useApolloClient, useMutation, useQuery } from "@apollo/client/react";
|
||||||
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-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 ResponsiveTable from "../responsive-table/responsive-table.component";
|
||||||
import { useForm } from "antd/es/form/Form";
|
import { useForm } from "antd/es/form/Form";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
@@ -27,6 +27,7 @@ import dayjs from "../../utils/day";
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
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 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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
|
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ const mapDispatchToProps = () => ({
|
|||||||
export function ShopEmployeesFormComponent({ bodyshop }) {
|
export function ShopEmployeesFormComponent({ bodyshop }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = useForm();
|
const [form] = useForm();
|
||||||
|
const employeeRates = Form.useWatch(["rates"], form) || [];
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const [deleteVacation] = useMutation(DELETE_VACATION);
|
const [deleteVacation] = useMutation(DELETE_VACATION);
|
||||||
@@ -311,58 +313,76 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
|
|||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
|
const employeeRate = employeeRates[field.name] || {};
|
||||||
<LayoutFormRow grow>
|
|
||||||
<Form.Item
|
return (
|
||||||
label={t("employees.fields.cost_center")}
|
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
|
||||||
key={`${index}`}
|
<LayoutFormRow
|
||||||
name={[field.name, "cost_center"]}
|
grow
|
||||||
valuePropName="value"
|
title={getFormListItemTitle(t("employees.fields.cost_center"), index, employeeRate.cost_center)}
|
||||||
rules={[
|
extra={
|
||||||
{
|
<Space align="center" size="small">
|
||||||
required: true
|
<Button
|
||||||
//message: t("general.validation.required"),
|
type="text"
|
||||||
}
|
icon={<DeleteFilled />}
|
||||||
]}
|
onClick={() => {
|
||||||
|
remove(field.name);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormListMoveArrows
|
||||||
|
move={move}
|
||||||
|
index={index}
|
||||||
|
total={fields.length}
|
||||||
|
orientation="horizontal"
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Select
|
<Form.Item
|
||||||
options={[
|
label={t("employees.fields.cost_center")}
|
||||||
{ value: "timetickets.labels.shift", label: t("timetickets.labels.shift") },
|
key={`${index}`}
|
||||||
...(bodyshop.cdk_dealerid ||
|
name={[field.name, "cost_center"]}
|
||||||
bodyshop.pbs_serialnumber ||
|
valuePropName="value"
|
||||||
bodyshop.rr_dealerid ||
|
rules={[
|
||||||
Enhanced_Payroll.treatment === "on"
|
{
|
||||||
? CiecaSelect(false, true)
|
required: true
|
||||||
: bodyshop.md_responsibility_centers.costs.map((c) => ({
|
//message: t("general.validation.required"),
|
||||||
value: c.name,
|
}
|
||||||
label: c.name
|
|
||||||
})))
|
|
||||||
]}
|
]}
|
||||||
/>
|
>
|
||||||
</Form.Item>
|
<Select
|
||||||
<Form.Item
|
options={[
|
||||||
label={t("employees.fields.rate")}
|
{ value: "timetickets.labels.shift", label: t("timetickets.labels.shift") },
|
||||||
key={`${index}`}
|
...(bodyshop.cdk_dealerid ||
|
||||||
name={[field.name, "rate"]}
|
bodyshop.pbs_serialnumber ||
|
||||||
rules={[
|
bodyshop.rr_dealerid ||
|
||||||
{
|
Enhanced_Payroll.treatment === "on"
|
||||||
required: true
|
? CiecaSelect(false, true)
|
||||||
//message: t("general.validation.required"),
|
: bodyshop.md_responsibility_centers.costs.map((c) => ({
|
||||||
}
|
value: c.name,
|
||||||
]}
|
label: c.name
|
||||||
>
|
})))
|
||||||
<InputNumber min={0} precision={2} />
|
]}
|
||||||
</Form.Item>
|
/>
|
||||||
<DeleteFilled
|
</Form.Item>
|
||||||
onClick={() => {
|
<Form.Item
|
||||||
remove(field.name);
|
label={t("employees.fields.rate")}
|
||||||
}}
|
key={`${index}`}
|
||||||
/>
|
name={[field.name, "rate"]}
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
rules={[
|
||||||
</LayoutFormRow>
|
{
|
||||||
</Form.Item>
|
required: true
|
||||||
))}
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber min={0} precision={2} />
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
|
|||||||
304
client/src/components/shop-info/shop-info.color.utils.js
Normal file
304
client/src/components/shop-info/shop-info.color.utils.js
Normal 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))`
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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))"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ import styled from "styled-components";
|
|||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import ConfigFormTypes from "../config-form-components/config-form-types";
|
import ConfigFormTypes from "../config-form-components/config-form-types";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
const SelectorDiv = styled.div`
|
const SelectorDiv = styled.div`
|
||||||
@@ -17,6 +18,8 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const TemplateListGenerated = TemplateList();
|
const TemplateListGenerated = TemplateList();
|
||||||
|
const intakeChecklistItems = Form.useWatch(["intakechecklist", "form"], form) || [];
|
||||||
|
const deliverChecklistItems = Form.useWatch(["deliverchecklist", "form"], form) || [];
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LayoutFormRow header={t("bodyshop.labels.intakechecklist")} id="intakechecklist">
|
<LayoutFormRow header={t("bodyshop.labels.intakechecklist")} id="intakechecklist">
|
||||||
@@ -24,104 +27,126 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
|||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key}>
|
const intakeChecklistItem = intakeChecklistItems[field.name] || {};
|
||||||
<LayoutFormRow noDivider>
|
|
||||||
<Form.Item
|
|
||||||
label={t("jobs.fields.intake.name")}
|
|
||||||
key={`${index}name`}
|
|
||||||
name={[field.name, "name"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("jobs.fields.intake.type")}
|
|
||||||
key={`${index}type`}
|
|
||||||
name={[field.name, "type"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select options={Object.keys(ConfigFormTypes).map((i) => ({ value: i, label: i }))} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("jobs.fields.intake.label")}
|
|
||||||
key={`${index}label`}
|
|
||||||
name={[field.name, "label"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item shouldUpdate>
|
return (
|
||||||
{() => {
|
<Form.Item key={field.key}>
|
||||||
if (form.getFieldValue(["intakechecklist", "form", index, "type"]) !== "slider") return null;
|
<LayoutFormRow
|
||||||
return (
|
noDivider
|
||||||
<>
|
title={getFormListItemTitle(
|
||||||
<Form.Item
|
t("jobs.fields.intake.name"),
|
||||||
label={t("jobs.fields.intake.min")}
|
index,
|
||||||
key={`${index}min`}
|
intakeChecklistItem.name,
|
||||||
name={[field.name, "min"]}
|
intakeChecklistItem.label
|
||||||
dependencies={[[field.name, "type"]]}
|
)}
|
||||||
rules={[
|
extra={
|
||||||
{
|
<Space align="center" size="small">
|
||||||
required: true
|
<Button
|
||||||
//message: t("general.validation.required"),
|
type="text"
|
||||||
}
|
icon={<DeleteFilled />}
|
||||||
]}
|
onClick={() => {
|
||||||
>
|
remove(field.name);
|
||||||
<InputNumber />
|
}}
|
||||||
</Form.Item>
|
/>
|
||||||
<Form.Item
|
<FormListMoveArrows
|
||||||
label={t("jobs.fields.intake.max")}
|
move={move}
|
||||||
key={`${index}max`}
|
index={index}
|
||||||
name={[field.name, "max"]}
|
total={fields.length}
|
||||||
rules={[
|
orientation="horizontal"
|
||||||
{
|
/>
|
||||||
required: true
|
</Space>
|
||||||
//message: t("general.validation.required"),
|
}
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber />
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("jobs.fields.intake.required")}
|
|
||||||
key={`${index}required`}
|
|
||||||
name={[field.name, "required"]}
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
>
|
||||||
<Switch />
|
<Form.Item
|
||||||
</Form.Item>
|
label={t("jobs.fields.intake.name")}
|
||||||
<Space wrap>
|
key={`${index}name`}
|
||||||
<DeleteFilled
|
name={[field.name, "name"]}
|
||||||
onClick={() => {
|
rules={[
|
||||||
remove(field.name);
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.intake.type")}
|
||||||
|
key={`${index}type`}
|
||||||
|
name={[field.name, "type"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select options={Object.keys(ConfigFormTypes).map((i) => ({ value: i, label: i }))} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.intake.label")}
|
||||||
|
key={`${index}label`}
|
||||||
|
name={[field.name, "label"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item shouldUpdate>
|
||||||
|
{() => {
|
||||||
|
if (form.getFieldValue(["intakechecklist", "form", index, "type"]) !== "slider")
|
||||||
|
return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.intake.min")}
|
||||||
|
key={`${index}min`}
|
||||||
|
name={[field.name, "min"]}
|
||||||
|
dependencies={[[field.name, "type"]]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.intake.max")}
|
||||||
|
key={`${index}max`}
|
||||||
|
name={[field.name, "max"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
</Form.Item>
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
<Form.Item
|
||||||
</Space>
|
label={t("jobs.fields.intake.required")}
|
||||||
</LayoutFormRow>
|
key={`${index}required`}
|
||||||
</Form.Item>
|
name={[field.name, "required"]}
|
||||||
))}
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
@@ -171,105 +196,129 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
|||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key}>
|
const deliverChecklistItem = deliverChecklistItems[field.name] || {};
|
||||||
<LayoutFormRow noDivider>
|
|
||||||
<Form.Item
|
|
||||||
label={t("jobs.fields.intake.name")}
|
|
||||||
key={`${index}named`}
|
|
||||||
name={[field.name, "name"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
return (
|
||||||
label={t("jobs.fields.intake.type")}
|
<Form.Item key={field.key}>
|
||||||
key={`${index}typed`}
|
<LayoutFormRow
|
||||||
name={[field.name, "type"]}
|
noDivider
|
||||||
rules={[
|
title={getFormListItemTitle(
|
||||||
{
|
t("jobs.fields.intake.name"),
|
||||||
required: true
|
index,
|
||||||
//message: t("general.validation.required"),
|
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>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Select options={Object.keys(ConfigFormTypes).map((i) => ({ value: i, label: i }))} />
|
<Form.Item
|
||||||
</Form.Item>
|
label={t("jobs.fields.intake.name")}
|
||||||
|
key={`${index}named`}
|
||||||
|
name={[field.name, "name"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("jobs.fields.intake.label")}
|
label={t("jobs.fields.intake.type")}
|
||||||
key={`${index}labeld`}
|
key={`${index}typed`}
|
||||||
name={[field.name, "label"]}
|
name={[field.name, "type"]}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true
|
required: true
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Select options={Object.keys(ConfigFormTypes).map((i) => ({ value: i, label: i }))} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item shouldUpdate>
|
<Form.Item
|
||||||
{() => {
|
label={t("jobs.fields.intake.label")}
|
||||||
if (form.getFieldValue(["deliverchecklist", "form", index, "type"]) !== "slider") return null;
|
key={`${index}labeld`}
|
||||||
return (
|
name={[field.name, "label"]}
|
||||||
<>
|
rules={[
|
||||||
<Form.Item
|
{
|
||||||
label={t("jobs.fields.intake.min")}
|
required: true
|
||||||
key={`${index}mind`}
|
//message: t("general.validation.required"),
|
||||||
name={[field.name, "min"]}
|
}
|
||||||
dependencies={[[field.name, "type"]]}
|
]}
|
||||||
rules={[
|
>
|
||||||
{
|
<Input />
|
||||||
required: form.getFieldValue([field.name, "type"]) === "slider"
|
</Form.Item>
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
<Form.Item shouldUpdate>
|
||||||
]}
|
{() => {
|
||||||
>
|
if (form.getFieldValue(["deliverchecklist", "form", index, "type"]) !== "slider")
|
||||||
<InputNumber />
|
return null;
|
||||||
</Form.Item>
|
return (
|
||||||
<Form.Item
|
<>
|
||||||
label={t("jobs.fields.intake.max")}
|
<Form.Item
|
||||||
key={`${index}maxd`}
|
label={t("jobs.fields.intake.min")}
|
||||||
name={[field.name, "max"]}
|
key={`${index}mind`}
|
||||||
dependencies={[[field.name, "type"]]}
|
name={[field.name, "min"]}
|
||||||
rules={[
|
dependencies={[[field.name, "type"]]}
|
||||||
{
|
rules={[
|
||||||
required: form.getFieldValue([field.name, "type"]) === "slider"
|
{
|
||||||
//message: t("general.validation.required"),
|
required: form.getFieldValue([field.name, "type"]) === "slider"
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
>
|
]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>
|
<InputNumber />
|
||||||
</>
|
</Form.Item>
|
||||||
);
|
<Form.Item
|
||||||
}}
|
label={t("jobs.fields.intake.max")}
|
||||||
</Form.Item>
|
key={`${index}maxd`}
|
||||||
<Form.Item
|
name={[field.name, "max"]}
|
||||||
label={t("jobs.fields.intake.required")}
|
dependencies={[[field.name, "type"]]}
|
||||||
key={`${index}requiredd`}
|
rules={[
|
||||||
name={[field.name, "required"]}
|
{
|
||||||
valuePropName="checked"
|
required: form.getFieldValue([field.name, "type"]) === "slider"
|
||||||
>
|
//message: t("general.validation.required"),
|
||||||
<Switch />
|
}
|
||||||
</Form.Item>
|
]}
|
||||||
<DeleteFilled
|
>
|
||||||
onClick={() => {
|
<InputNumber />
|
||||||
remove(field.name);
|
</Form.Item>
|
||||||
}}
|
</>
|
||||||
/>
|
);
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
}}
|
||||||
</LayoutFormRow>
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
))}
|
label={t("jobs.fields.intake.required")}
|
||||||
|
key={`${index}requiredd`}
|
||||||
|
name={[field.name, "required"]}
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ import { Button, Form, Input, Space } from "antd";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
export default function ShopInfoLaborRates() {
|
export default function ShopInfoLaborRates() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const laborRates = Form.useWatch(["md_labor_rates"], form) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -23,9 +26,32 @@ export default function ShopInfoLaborRates() {
|
|||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key}>
|
const laborRate = laborRates[field.name] || {};
|
||||||
<LayoutFormRow noDivider={index === 0}>
|
|
||||||
|
return (
|
||||||
|
<Form.Item key={field.key}>
|
||||||
|
<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
|
<Form.Item
|
||||||
label={t("jobs.fields.labor_rate_desc")}
|
label={t("jobs.fields.labor_rate_desc")}
|
||||||
key={`${index}rate_label`}
|
key={`${index}rate_label`}
|
||||||
@@ -314,17 +340,10 @@ export default function ShopInfoLaborRates() {
|
|||||||
>
|
>
|
||||||
<CurrencyInput min={0} />
|
<CurrencyInput min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Space>
|
</LayoutFormRow>
|
||||||
<DeleteFilled
|
</Form.Item>
|
||||||
onClick={() => {
|
);
|
||||||
remove(field.name);
|
})}
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.rate_length} />
|
|
||||||
</Space>
|
|
||||||
</LayoutFormRow>
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Button, Col, Form, Input, Row, Select, Space, Switch } from "antd";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
|
|
||||||
@@ -75,98 +76,144 @@ export default function ShopInfoPartsScan({ form }) {
|
|||||||
{fields.map((field, index) => {
|
{fields.map((field, index) => {
|
||||||
const selectedField = watchedFields?.[index]?.field || "line_desc";
|
const selectedField = watchedFields?.[index]?.field || "line_desc";
|
||||||
const fieldType = getFieldType(selectedField);
|
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 (
|
return (
|
||||||
<Form.Item key={field.key}>
|
<Form.Item key={field.key}>
|
||||||
<Row gutter={[16, 16]} align="middle">
|
<LayoutFormRow
|
||||||
{/* Select Field */}
|
noDivider
|
||||||
<Col span={6}>
|
title={getFormListItemTitle(
|
||||||
<Form.Item
|
t("bodyshop.fields.md_parts_scan.field"),
|
||||||
label={t("bodyshop.fields.md_parts_scan.field")}
|
index,
|
||||||
name={[field.name, "field"]}
|
ruleTitle,
|
||||||
rules={[
|
selectedFieldLabel
|
||||||
{
|
)}
|
||||||
required: true,
|
extra={
|
||||||
message: t("general.validation.required", {
|
<Space align="center" size="small">
|
||||||
label: t("bodyshop.fields.md_parts_scan.field")
|
<Button
|
||||||
})
|
type="text"
|
||||||
}
|
icon={<DeleteFilled />}
|
||||||
]}
|
onClick={() => {
|
||||||
>
|
remove(field.name);
|
||||||
<Select
|
|
||||||
options={fieldSelectOptions}
|
|
||||||
onChange={() => {
|
|
||||||
form.setFields([
|
|
||||||
{ name: ["md_parts_scan", index, "operation"], value: "contains" },
|
|
||||||
{ name: ["md_parts_scan", index, "value"], value: undefined }
|
|
||||||
]);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
<FormListMoveArrows
|
||||||
</Col>
|
move={move}
|
||||||
|
index={index}
|
||||||
{/* Operation */}
|
total={fields.length}
|
||||||
{fieldType !== "predefined" && fieldType && (
|
orientation="horizontal"
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 16]} align="middle">
|
||||||
|
{/* Select Field */}
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.md_parts_scan.operation")}
|
label={t("bodyshop.fields.md_parts_scan.field")}
|
||||||
name={[field.name, "operation"]}
|
name={[field.name, "field"]}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t("general.validation.required", {
|
message: t("general.validation.required", {
|
||||||
label: t("bodyshop.fields.md_parts_scan.operation")
|
label: t("bodyshop.fields.md_parts_scan.field")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select options={operationOptions[fieldType]} />
|
<Select
|
||||||
|
options={fieldSelectOptions}
|
||||||
|
onChange={() => {
|
||||||
|
form.setFields([
|
||||||
|
{ name: ["md_parts_scan", index, "operation"], value: "contains" },
|
||||||
|
{ name: ["md_parts_scan", index, "value"], value: undefined }
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Value */}
|
{/* Operation */}
|
||||||
{fieldType && (
|
{fieldType !== "predefined" && fieldType && (
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.md_parts_scan.value")}
|
label={t("bodyshop.fields.md_parts_scan.operation")}
|
||||||
name={[field.name, "value"]}
|
name={[field.name, "operation"]}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t("general.validation.required", {
|
message: t("general.validation.required", {
|
||||||
label: t("bodyshop.fields.md_parts_scan.value")
|
label: t("bodyshop.fields.md_parts_scan.operation")
|
||||||
})
|
})
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{fieldType === "predefined" ? (
|
|
||||||
<Select
|
|
||||||
options={
|
|
||||||
selectedField === "part_type"
|
|
||||||
? predefinedPartTypes.map((type) => ({
|
|
||||||
label: type,
|
|
||||||
value: type
|
|
||||||
}))
|
|
||||||
: predefinedModLbrTypes.map((type) => ({
|
|
||||||
label: type,
|
|
||||||
value: type
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
/>
|
]}
|
||||||
) : (
|
>
|
||||||
<Input />
|
<Select options={operationOptions[fieldType]} />
|
||||||
)}
|
</Form.Item>
|
||||||
</Form.Item>
|
</Col>
|
||||||
</Col>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Case Sensitivity */}
|
{/* Value */}
|
||||||
{fieldType === "string" && (
|
{fieldType && (
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.md_parts_scan.value")}
|
||||||
|
name={[field.name, "value"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t("general.validation.required", {
|
||||||
|
label: t("bodyshop.fields.md_parts_scan.value")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{fieldType === "predefined" ? (
|
||||||
|
<Select
|
||||||
|
options={
|
||||||
|
selectedField === "part_type"
|
||||||
|
? predefinedPartTypes.map((type) => ({
|
||||||
|
label: type,
|
||||||
|
value: type
|
||||||
|
}))
|
||||||
|
: predefinedModLbrTypes.map((type) => ({
|
||||||
|
label: type,
|
||||||
|
value: type
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Input />
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Case Sensitivity */}
|
||||||
|
{fieldType === "string" && (
|
||||||
|
<Col span={4}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.md_parts_scan.caseInsensitive")}
|
||||||
|
name={[field.name, "caseInsensitive"]}
|
||||||
|
valuePropName="checked"
|
||||||
|
labelCol={{ span: 14 }}
|
||||||
|
wrapperCol={{ span: 10 }}
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Mark Line as Critical */}
|
||||||
<Col span={4}>
|
<Col span={4}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.md_parts_scan.caseInsensitive")}
|
label={t("bodyshop.fields.md_parts_scan.mark_critical")}
|
||||||
name={[field.name, "caseInsensitive"]}
|
name={[field.name, "mark_critical"]}
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
labelCol={{ span: 14 }}
|
labelCol={{ span: 14 }}
|
||||||
wrapperCol={{ span: 10 }}
|
wrapperCol={{ span: 10 }}
|
||||||
@@ -174,65 +221,44 @@ export default function ShopInfoPartsScan({ form }) {
|
|||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Mark Line as Critical */}
|
{/* Update Field */}
|
||||||
<Col span={4}>
|
<Col span={4}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.md_parts_scan.mark_critical")}
|
label={t("bodyshop.fields.md_parts_scan.update_field")}
|
||||||
name={[field.name, "mark_critical"]}
|
name={[field.name, "update_field"]}
|
||||||
valuePropName="checked"
|
>
|
||||||
labelCol={{ span: 14 }}
|
<Select
|
||||||
wrapperCol={{ span: 10 }}
|
options={fieldSelectOptions}
|
||||||
>
|
allowClear
|
||||||
<Switch />
|
onClear={() =>
|
||||||
</Form.Item>
|
form.setFields([{ name: ["md_parts_scan", index, "update_field"], value: null }])
|
||||||
</Col>
|
}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
|
||||||
{/* Update Field */}
|
{/* Update Field */}
|
||||||
<Col span={4}>
|
<Col span={4}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.md_parts_scan.update_field")}
|
label={t("bodyshop.fields.md_parts_scan.update_value")}
|
||||||
name={[field.name, "update_field"]}
|
name={[field.name, "update_value"]}
|
||||||
>
|
dependencies={[["md_parts_scan", index, "update_field"]]}
|
||||||
<Select
|
tooltip={t("bodyshop.tooltips.md_parts_scan.update_value_tooltip")}
|
||||||
options={fieldSelectOptions}
|
rules={[
|
||||||
allowClear
|
{
|
||||||
onClear={() =>
|
required: form.getFieldValue(["md_parts_scan", index, "update_field"]),
|
||||||
form.setFields([{ name: ["md_parts_scan", index, "update_field"], value: null }])
|
message: t("general.validation.required", {
|
||||||
}
|
label: t("bodyshop.fields.md_parts_scan.update_value")
|
||||||
/>
|
})
|
||||||
</Form.Item>
|
}
|
||||||
</Col>
|
]}
|
||||||
|
>
|
||||||
{/* Update Field */}
|
<Input />
|
||||||
<Col span={4}>
|
</Form.Item>
|
||||||
<Form.Item
|
</Col>
|
||||||
label={t("bodyshop.fields.md_parts_scan.update_value")}
|
</Row>
|
||||||
name={[field.name, "update_value"]}
|
</LayoutFormRow>
|
||||||
dependencies={[["md_parts_scan", index, "update_field"]]}
|
|
||||||
tooltip={t("bodyshop.tooltips.md_parts_scan.update_value_tooltip")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: form.getFieldValue(["md_parts_scan", index, "update_field"]),
|
|
||||||
message: t("general.validation.required", {
|
|
||||||
label: t("bodyshop.fields.md_parts_scan.update_value")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,11 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { Button, Form, Select, Space } from "antd";
|
import { Button, Form, Select, Space } from "antd";
|
||||||
import { useState } from "react";
|
|
||||||
import { ChromePicker } from "react-color";
|
import { ChromePicker } from "react-color";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import styled from "styled-components";
|
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 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 { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -28,6 +29,11 @@ const SelectorDiv = styled.div`
|
|||||||
|
|
||||||
export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
||||||
const { t } = useTranslation();
|
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 {
|
const {
|
||||||
treatments: { Production_List_Status_Colors }
|
treatments: { Production_List_Status_Colors }
|
||||||
@@ -37,23 +43,6 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
splitKey: bodyshop.imexshopid
|
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 (
|
return (
|
||||||
<SelectorDiv id="jobstatus">
|
<SelectorDiv id="jobstatus">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -67,7 +56,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select mode="tags" onBlur={handleBlur} />
|
<Select mode="tags" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "active_statuses"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "pre_production_statuses"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "production_statuses"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "post_production_statuses"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "ready_statuses"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_ro_statuses", "additional_board_statuses"]}
|
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>
|
</Form.Item>
|
||||||
<LayoutFormRow noDivider>
|
<LayoutFormRow noDivider>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -158,7 +147,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_scheduled"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_arrived")}
|
label={t("bodyshop.fields.statuses.default_arrived")}
|
||||||
@@ -170,7 +159,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_arrived"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_exported")}
|
label={t("bodyshop.fields.statuses.default_exported")}
|
||||||
@@ -182,7 +171,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_exported"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_imported")}
|
label={t("bodyshop.fields.statuses.default_imported")}
|
||||||
@@ -194,7 +183,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_imported"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_invoiced")}
|
label={t("bodyshop.fields.statuses.default_invoiced")}
|
||||||
@@ -206,7 +195,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_invoiced"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_completed")}
|
label={t("bodyshop.fields.statuses.default_completed")}
|
||||||
@@ -218,7 +207,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_completed"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_delivered")}
|
label={t("bodyshop.fields.statuses.default_delivered")}
|
||||||
@@ -230,7 +219,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_delivered"]}
|
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>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.statuses.default_void")}
|
label={t("bodyshop.fields.statuses.default_void")}
|
||||||
@@ -242,7 +231,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
]}
|
]}
|
||||||
name={["md_ro_statuses", "default_void"]}
|
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>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
{Production_List_Status_Colors.treatment === "on" && (
|
{Production_List_Status_Colors.treatment === "on" && (
|
||||||
@@ -251,13 +240,41 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
{(fields, { add, remove }) => {
|
{(fields, { add, remove }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Space size="large" wrap>
|
<Space size="large" wrap align="start">
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key}>
|
const productionColor = productionColors[field.name] || {};
|
||||||
<Space orientation="vertical">
|
const productionColorSurfaceStyles = getTintedCardSurfaceStyles(productionColor.color);
|
||||||
<div style={{ display: "flex" }}>
|
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
|
<Form.Item
|
||||||
style={{ flex: 1 }}
|
|
||||||
label={t("jobs.fields.status")}
|
label={t("jobs.fields.status")}
|
||||||
key={`${index}status`}
|
key={`${index}status`}
|
||||||
name={[field.name, "status"]}
|
name={[field.name, "status"]}
|
||||||
@@ -268,36 +285,35 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select options={productionStatus.map((item) => ({ value: item, label: item }))} />
|
<Select
|
||||||
|
options={productionColorStatusOptions.map((item) => ({ value: item, label: item }))}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.statuses.color")}
|
||||||
|
key={`${index}color`}
|
||||||
|
name={[field.name, "color"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<ColorPicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<DeleteFilled
|
|
||||||
onClick={() => {
|
|
||||||
remove(field.name);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Form.Item
|
</LayoutFormRow>
|
||||||
label={t("bodyshop.fields.statuses.color")}
|
);
|
||||||
key={`${index}color`}
|
})}
|
||||||
name={[field.name, "color"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<ColorPicker />
|
|
||||||
</Form.Item>
|
|
||||||
</Space>
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
</Space>
|
</Space>
|
||||||
<Form.Item>
|
<Form.Item style={{ marginTop: 8 }}>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
add();
|
add({
|
||||||
|
color: { ...DEFAULT_TRANSLUCENT_CARD_COLOR }
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
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 { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -7,8 +7,14 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
|
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import { ColorPicker } from "./shop-info.rostatus.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({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -17,12 +23,24 @@ const mapDispatchToProps = () => ({
|
|||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//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 }) {
|
export function ShopInfoSchedulingComponent({ form, bodyshop }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const appointmentColors = Form.useWatch(["appt_colors"], form) || form.getFieldValue(["appt_colors"]) || [];
|
||||||
|
const schedulingBuckets = Form.useWatch(["ssbuckets"], form) || form.getFieldValue(["ssbuckets"]) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LayoutFormRow id="shopinfo-scheduling">
|
<LayoutFormRow grow header={t("bodyshop.labels.scheduling")} id="shopinfo-scheduling">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.appt_length")}
|
label={t("bodyshop.fields.appt_length")}
|
||||||
name={"appt_length"}
|
name={"appt_length"}
|
||||||
@@ -100,80 +118,93 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
|
|||||||
<Select mode="tags" />
|
<Select mode="tags" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Divider titlePlacement="left">{t("bodyshop.labels.workingdays")}</Divider>
|
<LayoutFormRow header={t("bodyshop.labels.workingdays")} id="workingdays">
|
||||||
<Space wrap size="large" id="workingdays">
|
<Space wrap size="middle">
|
||||||
<Form.Item label={t("general.labels.sunday")} name={["workingdays", "sunday"]} valuePropName="checked">
|
{WORKING_DAYS.map(({ key, labelKey }) => (
|
||||||
<Switch />
|
<Form.Item key={key} label={t(labelKey)} name={["workingdays", key]} valuePropName="checked">
|
||||||
</Form.Item>
|
<Switch />
|
||||||
<Form.Item label={t("general.labels.monday")} name={["workingdays", "monday"]} valuePropName="checked">
|
</Form.Item>
|
||||||
<Switch />
|
))}
|
||||||
</Form.Item>
|
</Space>
|
||||||
<Form.Item label={t("general.labels.tuesday")} name={["workingdays", "tuesday"]} valuePropName="checked">
|
</LayoutFormRow>
|
||||||
<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">
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
</Space>
|
|
||||||
<LayoutFormRow header={t("bodyshop.labels.apptcolors")} id="apptcolors">
|
<LayoutFormRow header={t("bodyshop.labels.apptcolors")} id="apptcolors">
|
||||||
<Form.List name={["appt_colors"]}>
|
<Form.List name={["appt_colors"]}>
|
||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key}>
|
const appointmentColor =
|
||||||
<LayoutFormRow noDivider>
|
appointmentColors[field.name] || form.getFieldValue(["appt_colors", field.name]) || {};
|
||||||
<Form.Item
|
const appointmentColorSurfaceStyles = getTintedCardSurfaceStyles(appointmentColor.color);
|
||||||
label={t("bodyshop.fields.appt_colors.label")}
|
|
||||||
key={`${index}aptcolorlabel`}
|
return (
|
||||||
name={[field.name, "label"]}
|
<Form.Item key={field.key}>
|
||||||
rules={[
|
<LayoutFormRow
|
||||||
{
|
noDivider
|
||||||
required: true
|
title={getFormListItemTitle(
|
||||||
//message: t("general.validation.required"),
|
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}
|
||||||
>
|
>
|
||||||
<Input />
|
<Form.Item
|
||||||
</Form.Item>
|
label={t("bodyshop.fields.appt_colors.label")}
|
||||||
<Form.Item
|
key={`${index}aptcolorlabel`}
|
||||||
label={t("bodyshop.fields.appt_colors.color")}
|
name={[field.name, "label"]}
|
||||||
key={`${index}aptcolorcolor`}
|
rules={[
|
||||||
name={[field.name, "color"]}
|
{
|
||||||
rules={[
|
required: true
|
||||||
{
|
//message: t("general.validation.required"),
|
||||||
required: true
|
}
|
||||||
//message: t("general.validation.required"),
|
]}
|
||||||
}
|
>
|
||||||
]}
|
<Input />
|
||||||
>
|
</Form.Item>
|
||||||
<ColorpickerFormItemComponent />
|
<Form.Item
|
||||||
</Form.Item>
|
label={t("bodyshop.fields.appt_colors.color")}
|
||||||
<Space wrap>
|
key={`${index}aptcolorcolor`}
|
||||||
<DeleteFilled
|
name={[field.name, "color"]}
|
||||||
onClick={() => {
|
rules={[
|
||||||
remove(field.name);
|
{
|
||||||
}}
|
required: true
|
||||||
/>
|
//message: t("general.validation.required"),
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
}
|
||||||
</Space>
|
]}
|
||||||
</LayoutFormRow>
|
>
|
||||||
</Form.Item>
|
<ColorpickerFormItemComponent />
|
||||||
))}
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
add();
|
add({
|
||||||
|
color: {
|
||||||
|
...DEFAULT_TRANSLUCENT_PICKER_COLOR,
|
||||||
|
rgb: { ...DEFAULT_TRANSLUCENT_PICKER_COLOR.rgb }
|
||||||
|
}
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
>
|
>
|
||||||
@@ -191,73 +222,103 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
|
|||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key}>
|
const schedulingBucket =
|
||||||
<LayoutFormRow noDivider>
|
schedulingBuckets[field.name] || form.getFieldValue(["ssbuckets", field.name]) || {};
|
||||||
<Form.Item
|
const schedulingBucketSurfaceStyles = getTintedCardSurfaceStyles(schedulingBucket.color);
|
||||||
label={t("bodyshop.fields.ssbuckets.id")}
|
|
||||||
key={`${index}id`}
|
|
||||||
name={[field.name, "id"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("bodyshop.fields.ssbuckets.label")}
|
|
||||||
key={`${index}label`}
|
|
||||||
name={[field.name, "label"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
return (
|
||||||
label={t("bodyshop.fields.ssbuckets.gte")}
|
<Form.Item key={field.key}>
|
||||||
key={`${index}gte`}
|
<LayoutFormRow
|
||||||
name={[field.name, "gte"]}
|
noDivider
|
||||||
rules={[
|
title={getFormListItemTitle(
|
||||||
{
|
t("bodyshop.fields.ssbuckets.label"),
|
||||||
required: true
|
index,
|
||||||
//message: t("general.validation.required"),
|
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}
|
||||||
>
|
>
|
||||||
<InputNumber />
|
<Form.Item
|
||||||
</Form.Item>
|
label={t("bodyshop.fields.ssbuckets.id")}
|
||||||
|
key={`${index}id`}
|
||||||
|
name={[field.name, "id"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.ssbuckets.label")}
|
||||||
|
key={`${index}label`}
|
||||||
|
name={[field.name, "label"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.ssbuckets.lt")}
|
label={t("bodyshop.fields.ssbuckets.gte")}
|
||||||
key={`${index}lt`}
|
key={`${index}gte`}
|
||||||
name={[field.name, "lt"]}
|
name={[field.name, "gte"]}
|
||||||
>
|
rules={[
|
||||||
<InputNumber />
|
{
|
||||||
</Form.Item>
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.ssbuckets.target")}
|
label={t("bodyshop.fields.ssbuckets.lt")}
|
||||||
key={`${index}target`}
|
key={`${index}lt`}
|
||||||
name={[field.name, "target"]}
|
name={[field.name, "lt"]}
|
||||||
rules={[
|
>
|
||||||
{
|
<InputNumber />
|
||||||
required: true
|
</Form.Item>
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
<Form.Item
|
||||||
]}
|
label={t("bodyshop.fields.ssbuckets.target")}
|
||||||
>
|
key={`${index}target`}
|
||||||
<InputNumber />
|
name={[field.name, "target"]}
|
||||||
</Form.Item>
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Space orientation="horizontal">
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={
|
label={
|
||||||
<Space>
|
<Space>
|
||||||
@@ -284,23 +345,17 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
|
|||||||
>
|
>
|
||||||
<ColorPicker />
|
<ColorPicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Space wrap>
|
</LayoutFormRow>
|
||||||
<DeleteFilled
|
</Form.Item>
|
||||||
onClick={() => {
|
);
|
||||||
remove(field.name);
|
})}
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
|
||||||
</Space>
|
|
||||||
</Space>
|
|
||||||
</LayoutFormRow>
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
add();
|
add({
|
||||||
|
color: { ...DEFAULT_TRANSLUCENT_CARD_COLOR }
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ import { Button, Form, Input, Select, Space } from "antd";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
|
||||||
export default function ShopInfoSpeedPrint() {
|
export default function ShopInfoSpeedPrint() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
const allTemplates = TemplateList("job");
|
const allTemplates = TemplateList("job");
|
||||||
|
const speedPrintItems = Form.useWatch(["speedprint"], form) || [];
|
||||||
const TemplateListGenerated = InstanceRenderManager({
|
const TemplateListGenerated = InstanceRenderManager({
|
||||||
imex: Object.fromEntries(Object.entries(allTemplates).filter(([, { enhanced_payroll }]) => !enhanced_payroll)),
|
imex: Object.fromEntries(Object.entries(allTemplates).filter(([, { enhanced_payroll }]) => !enhanced_payroll)),
|
||||||
rome: allTemplates
|
rome: allTemplates
|
||||||
@@ -19,67 +22,82 @@ export default function ShopInfoSpeedPrint() {
|
|||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
|
const speedPrintItem = speedPrintItems[field.name] || {};
|
||||||
<LayoutFormRow grow>
|
|
||||||
<Form.Item
|
|
||||||
label={t("bodyshop.fields.speedprint.id")}
|
|
||||||
key={`${index}id`}
|
|
||||||
name={[field.name, "id"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("bodyshop.fields.speedprint.label")}
|
|
||||||
key={`${index}label`}
|
|
||||||
name={[field.name, "label"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
return (
|
||||||
name={[field.name, "templates"]}
|
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
|
||||||
label={t("bodyshop.fields.speedprint.templates")}
|
<LayoutFormRow
|
||||||
rules={[
|
grow
|
||||||
{
|
title={getFormListItemTitle(
|
||||||
required: true,
|
t("bodyshop.fields.speedprint.label"),
|
||||||
//message: t("general.validation.required"),
|
index,
|
||||||
type: "array"
|
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>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Select
|
<Form.Item
|
||||||
mode="multiple"
|
label={t("bodyshop.fields.speedprint.id")}
|
||||||
options={Object.keys(TemplateListGenerated).map((key) => ({
|
key={`${index}id`}
|
||||||
value: TemplateListGenerated[key].key,
|
name={[field.name, "id"]}
|
||||||
label: TemplateListGenerated[key].title
|
rules={[
|
||||||
}))}
|
{
|
||||||
/>
|
required: true
|
||||||
</Form.Item>
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.speedprint.label")}
|
||||||
|
key={`${index}label`}
|
||||||
|
name={[field.name, "label"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Form.Item
|
||||||
<DeleteFilled
|
name={[field.name, "templates"]}
|
||||||
onClick={() => {
|
label={t("bodyshop.fields.speedprint.templates")}
|
||||||
remove(field.name);
|
rules={[
|
||||||
}}
|
{
|
||||||
/>
|
required: true,
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
//message: t("general.validation.required"),
|
||||||
</Space>
|
type: "array"
|
||||||
</LayoutFormRow>
|
}
|
||||||
</Form.Item>
|
]}
|
||||||
))}
|
>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
options={Object.keys(TemplateListGenerated).map((key) => ({
|
||||||
|
value: TemplateListGenerated[key].key,
|
||||||
|
label: TemplateListGenerated[key].title
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { DeleteFilled } from "@ant-design/icons";
|
|||||||
import { Button, Checkbox, Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd";
|
import { Button, Checkbox, Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.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 LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -55,6 +56,8 @@ const getTaskPresetAllocationErrors = (presets = [], t) => {
|
|||||||
|
|
||||||
export function ShopInfoTaskPresets({ bodyshop }) {
|
export function ShopInfoTaskPresets({ bodyshop }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const taskPresets = Form.useWatch(["md_tasks_presets", "presets"], form) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -93,152 +96,173 @@ export function ShopInfoTaskPresets({ bodyshop }) {
|
|||||||
{(fields, { add, remove, move }, { errors }) => {
|
{(fields, { add, remove, move }, { errors }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key}>
|
const taskPreset = taskPresets[field.name] || {};
|
||||||
<LayoutFormRow noDivider>
|
|
||||||
<Form.Item
|
return (
|
||||||
label={t("bodyshop.fields.md_tasks_presets.name")}
|
<Form.Item key={field.key}>
|
||||||
key={`${index}name`}
|
<LayoutFormRow
|
||||||
name={[field.name, "name"]}
|
noDivider
|
||||||
rules={[
|
title={getFormListItemTitle(
|
||||||
{
|
t("bodyshop.fields.md_tasks_presets.name"),
|
||||||
required: true
|
index,
|
||||||
//message: t("general.validation.required"),
|
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>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Input />
|
<Form.Item
|
||||||
</Form.Item>
|
label={t("bodyshop.fields.md_tasks_presets.name")}
|
||||||
<Form.Item
|
key={`${index}name`}
|
||||||
span={12}
|
name={[field.name, "name"]}
|
||||||
label={t("bodyshop.fields.md_tasks_presets.hourstype")}
|
rules={[
|
||||||
key={`${index}hourstype`}
|
{
|
||||||
name={[field.name, "hourstype"]}
|
required: true
|
||||||
rules={[
|
//message: t("general.validation.required"),
|
||||||
{
|
}
|
||||||
required: true
|
]}
|
||||||
//message: t("general.validation.required"),
|
>
|
||||||
}
|
<Input />
|
||||||
]}
|
</Form.Item>
|
||||||
>
|
<Form.Item
|
||||||
<Checkbox.Group>
|
span={12}
|
||||||
<Row>
|
label={t("bodyshop.fields.md_tasks_presets.hourstype")}
|
||||||
<Col span={4}>
|
key={`${index}hourstype`}
|
||||||
<Checkbox value="LAA" style={{ lineHeight: "32px" }}>
|
name={[field.name, "hourstype"]}
|
||||||
{t("joblines.fields.lbr_types.LAA")}
|
rules={[
|
||||||
</Checkbox>
|
{
|
||||||
</Col>
|
required: true
|
||||||
<Col span={4}>
|
//message: t("general.validation.required"),
|
||||||
<Checkbox value="LAB" style={{ lineHeight: "32px" }}>
|
}
|
||||||
{t("joblines.fields.lbr_types.LAB")}
|
]}
|
||||||
</Checkbox>
|
>
|
||||||
</Col>
|
<Checkbox.Group>
|
||||||
<Col span={4}>
|
<Row>
|
||||||
<Checkbox value="LAD" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LAD")}
|
<Checkbox value="LAA" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LAA")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LAE" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LAE")}
|
<Checkbox value="LAB" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LAB")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LAF" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LAF")}
|
<Checkbox value="LAD" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LAD")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LAG" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LAG")}
|
<Checkbox value="LAE" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LAE")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LAM" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LAM")}
|
<Checkbox value="LAF" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LAF")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LAR" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LAR")}
|
<Checkbox value="LAG" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LAG")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LAS" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LAS")}
|
<Checkbox value="LAM" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LAM")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LAU" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LAU")}
|
<Checkbox value="LAR" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LAR")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LA1" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LA1")}
|
<Checkbox value="LAS" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LAS")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LA2" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LA2")}
|
<Checkbox value="LAU" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LAU")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LA3" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LA3")}
|
<Checkbox value="LA1" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LA1")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
<Col span={4}>
|
</Col>
|
||||||
<Checkbox value="LA4" style={{ lineHeight: "32px" }}>
|
<Col span={4}>
|
||||||
{t("joblines.fields.lbr_types.LA4")}
|
<Checkbox value="LA2" style={{ lineHeight: "32px" }}>
|
||||||
</Checkbox>
|
{t("joblines.fields.lbr_types.LA2")}
|
||||||
</Col>
|
</Checkbox>
|
||||||
</Row>
|
</Col>
|
||||||
</Checkbox.Group>
|
<Col span={4}>
|
||||||
</Form.Item>
|
<Checkbox value="LA3" style={{ lineHeight: "32px" }}>
|
||||||
<Form.Item
|
{t("joblines.fields.lbr_types.LA3")}
|
||||||
label={t("bodyshop.fields.md_tasks_presets.percent")}
|
</Checkbox>
|
||||||
key={`${index}percent`}
|
</Col>
|
||||||
rules={[
|
<Col span={4}>
|
||||||
{
|
<Checkbox value="LA4" style={{ lineHeight: "32px" }}>
|
||||||
required: true
|
{t("joblines.fields.lbr_types.LA4")}
|
||||||
//message: t("general.validation.required"),
|
</Checkbox>
|
||||||
}
|
</Col>
|
||||||
]}
|
</Row>
|
||||||
name={[field.name, "percent"]}
|
</Checkbox.Group>
|
||||||
>
|
</Form.Item>
|
||||||
<InputNumber min={0} max={100} />
|
<Form.Item
|
||||||
</Form.Item>
|
label={t("bodyshop.fields.md_tasks_presets.percent")}
|
||||||
<Form.Item
|
key={`${index}percent`}
|
||||||
label={t("bodyshop.fields.md_tasks_presets.memo")}
|
rules={[
|
||||||
key={`${index}memo`}
|
{
|
||||||
name={[field.name, "memo"]}
|
required: true
|
||||||
>
|
//message: t("general.validation.required"),
|
||||||
<Input />
|
}
|
||||||
</Form.Item>
|
]}
|
||||||
<Form.Item
|
name={[field.name, "percent"]}
|
||||||
label={t("bodyshop.fields.md_tasks_presets.nextstatus")}
|
>
|
||||||
key={`${index}nextstatus`}
|
<InputNumber min={0} max={100} />
|
||||||
name={[field.name, "nextstatus"]}
|
</Form.Item>
|
||||||
>
|
<Form.Item
|
||||||
<Select
|
label={t("bodyshop.fields.md_tasks_presets.memo")}
|
||||||
options={bodyshop.md_ro_statuses.production_statuses.map((o) => ({
|
key={`${index}memo`}
|
||||||
value: o,
|
name={[field.name, "memo"]}
|
||||||
label: o
|
>
|
||||||
}))}
|
<Input />
|
||||||
/>
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
<Space wrap>
|
label={t("bodyshop.fields.md_tasks_presets.nextstatus")}
|
||||||
<DeleteFilled
|
key={`${index}nextstatus`}
|
||||||
onClick={() => {
|
name={[field.name, "nextstatus"]}
|
||||||
remove(field.name);
|
>
|
||||||
}}
|
<Select
|
||||||
/>
|
options={bodyshop.md_ro_statuses.production_statuses.map((o) => ({
|
||||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
value: o,
|
||||||
</Space>
|
label: o
|
||||||
</LayoutFormRow>
|
}))}
|
||||||
</Form.Item>
|
/>
|
||||||
))}
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<Form.ErrorList errors={errors} />
|
<Form.ErrorList errors={errors} />
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user