IO-3624 Extract shared title-row UI and polish config forms

This commit is contained in:
Dave
2026-03-24 20:56:30 -04:00
parent 1102670e66
commit 866e9581c2
23 changed files with 2706 additions and 1680 deletions

View File

@@ -1,5 +1,5 @@
import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Input, InputNumber, Select, Space, Switch, TimePicker } from "antd";
import { DeleteFilled, ReloadOutlined } from "@ant-design/icons";
import { Button, Form, Input, InputNumber, Select, Space, Switch, TimePicker, Tooltip } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -7,7 +7,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { ColorPicker } from "./shop-info.rostatus.component";
import {
@@ -15,6 +14,7 @@ import {
DEFAULT_TRANSLUCENT_PICKER_COLOR,
getTintedCardSurfaceStyles
} from "./shop-info.color.utils";
import "./shop-info.scheduling.styles.scss";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -33,6 +33,106 @@ const WORKING_DAYS = [
{ key: "saturday", labelKey: "general.labels.saturday" }
];
const APPOINTMENT_COLOR_PICKER_STYLES = {
default: {
wrap: {
display: "flex",
flexWrap: "wrap",
gap: "12px",
alignItems: "flex-start"
},
hue: {
flex: "1 1 180px",
height: "12px",
position: "relative",
marginTop: "20px"
},
swatches: {
flex: "1 1 160px"
}
}
};
const SCHEDULING_BUCKET_COLOR_PICKER_STYLES = {
default: {
picker: {
width: "100%",
height: "100%",
background: "color-mix(in srgb, var(--imex-form-surface) 92%, transparent)",
boxShadow: "none",
border: "1px solid color-mix(in srgb, var(--imex-form-surface-border) 72%, transparent)",
borderRadius: "8px",
boxSizing: "border-box",
overflow: "hidden"
},
saturation: {
width: "100%",
paddingBottom: "48%",
position: "relative",
borderRadius: "8px 8px 0 0",
overflow: "hidden"
},
body: {
padding: "12px"
},
controls: {
display: "flex",
gap: "10px"
},
color: {
width: "28px"
},
swatch: {
marginTop: "0",
width: "12px",
height: "12px",
borderRadius: "999px"
},
toggles: {
flex: "1"
},
hue: {
height: "10px",
position: "relative",
marginBottom: "8px"
},
alpha: {
height: "10px",
position: "relative"
}
}
};
const SECTION_TITLE_INPUT_STYLE = {
background: "color-mix(in srgb, var(--imex-form-surface) 78%, transparent)",
border: "1px solid color-mix(in srgb, var(--imex-form-surface-border) 72%, transparent)",
borderRadius: 6,
fontWeight: 500
};
const SECTION_TITLE_INPUT_ROW_STYLE = {
display: "flex",
gap: 8,
flexWrap: "wrap",
alignItems: "center",
minWidth: 180,
maxWidth: "100%"
};
const SECTION_TITLE_INPUT_GROUP_STYLE = {
display: "flex",
alignItems: "center",
gap: 6,
minWidth: 0
};
const SECTION_TITLE_INPUT_LABEL_STYLE = {
fontSize: 12,
lineHeight: 1.1,
opacity: 0.75,
whiteSpace: "nowrap"
};
export function ShopInfoSchedulingComponent({ form, bodyshop }) {
const { t } = useTranslation();
const appointmentColors = Form.useWatch(["appt_colors"], form) || form.getFieldValue(["appt_colors"]) || [];
@@ -141,11 +241,27 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
<Form.Item key={field.key}>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("bodyshop.fields.appt_colors.label"),
index,
appointmentColor.label
)}
title={
<div style={{ minWidth: 180, maxWidth: "100%" }}>
<Form.Item
noStyle
key={`${index}aptcolorlabel`}
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("bodyshop.fields.appt_colors.label")}
style={SECTION_TITLE_INPUT_STYLE}
/>
</Form.Item>
</div>
}
extra={
<Space align="center" size="small">
<Button
@@ -166,20 +282,6 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
{...appointmentColorSurfaceStyles}
>
<Form.Item
label={t("bodyshop.fields.appt_colors.label")}
key={`${index}aptcolorlabel`}
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.appt_colors.color")}
key={`${index}aptcolorcolor`}
name={[field.name, "color"]}
rules={[
@@ -189,7 +291,7 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
}
]}
>
<ColorpickerFormItemComponent />
<ColorpickerFormItemComponent styles={APPOINTMENT_COLOR_PICKER_STYLES} />
</Form.Item>
</LayoutFormRow>
</Form.Item>
@@ -231,12 +333,64 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
<Form.Item key={field.key}>
<LayoutFormRow
noDivider
title={getFormListItemTitle(
t("bodyshop.fields.ssbuckets.label"),
index,
schedulingBucket.label,
schedulingBucket.id
)}
title={
<div style={SECTION_TITLE_INPUT_ROW_STYLE}>
<div style={SECTION_TITLE_INPUT_GROUP_STYLE}>
<div style={SECTION_TITLE_INPUT_LABEL_STYLE}>{t("bodyshop.fields.ssbuckets.id")}</div>
<Form.Item
noStyle
key={`${index}id`}
name={[field.name, "id"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("bodyshop.fields.ssbuckets.id")}
style={{
...SECTION_TITLE_INPUT_STYLE,
width: 72
}}
/>
</Form.Item>
</div>
<div
style={{
...SECTION_TITLE_INPUT_GROUP_STYLE,
flex: 1,
minWidth: 0
}}
>
<div style={SECTION_TITLE_INPUT_LABEL_STYLE}>
{t("bodyshop.fields.ssbuckets.label")}
</div>
<Form.Item
noStyle
key={`${index}label`}
name={[field.name, "label"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input
size="small"
placeholder={t("bodyshop.fields.ssbuckets.label")}
style={{
...SECTION_TITLE_INPUT_STYLE,
width: "100%"
}}
/>
</Form.Item>
</div>
</div>
}
extra={
<Space align="center" size="small">
<Button
@@ -246,85 +400,10 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
{...schedulingBucketSurfaceStyles}
>
<Form.Item
label={t("bodyshop.fields.ssbuckets.id")}
key={`${index}id`}
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
label={t("bodyshop.fields.ssbuckets.gte")}
key={`${index}gte`}
name={[field.name, "gte"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.ssbuckets.lt")}
key={`${index}lt`}
name={[field.name, "lt"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.ssbuckets.target")}
key={`${index}target`}
name={[field.name, "target"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={
<Space>
{t("bodyshop.fields.ssbuckets.color")}
<Tooltip title={t("bodyshop.tooltips.reset-color")}>
<Button
size="small"
type="text"
icon={<ReloadOutlined />}
onClick={() => {
form.setFieldValue(["ssbuckets", field.name, "color"]);
@@ -335,16 +414,62 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
}
]);
}}
>
Reset
</Button>
</Space>
}
key={`${index}color`}
name={[field.name, "color"]}
>
<ColorPicker />
</Form.Item>
/>
</Tooltip>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
orientation="horizontal"
/>
</Space>
}
{...schedulingBucketSurfaceStyles}
>
<div className="shop-info-scheduling__bucket-card-body">
<div className="shop-info-scheduling__bucket-card-fields">
<Form.Item
label={t("bodyshop.fields.ssbuckets.gte")}
key={`${index}gte`}
name={[field.name, "gte"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.ssbuckets.lt")}
key={`${index}lt`}
name={[field.name, "lt"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.ssbuckets.target")}
key={`${index}target`}
name={[field.name, "target"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
</div>
<div className="shop-info-scheduling__bucket-card-color">
<Form.Item key={`${index}color`} name={[field.name, "color"]}>
<ColorPicker styles={SCHEDULING_BUCKET_COLOR_PICKER_STYLES} />
</Form.Item>
</div>
</div>
</LayoutFormRow>
</Form.Item>
);