|
|
|
|
@@ -1,4 +1,7 @@
|
|
|
|
|
import { DeleteFilled } from "@ant-design/icons";
|
|
|
|
|
import { CloseOutlined, DeleteFilled } from "@ant-design/icons";
|
|
|
|
|
import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
|
|
|
|
|
import { arrayMove, rectSortingStrategy, SortableContext, useSortable } from "@dnd-kit/sortable";
|
|
|
|
|
import { CSS } from "@dnd-kit/utilities";
|
|
|
|
|
import { Button, Form, Select, Space } from "antd";
|
|
|
|
|
import { ChromePicker } from "react-color";
|
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
@@ -42,14 +45,152 @@ const SelectorDiv = styled.div`
|
|
|
|
|
.production-status-color-title-select .ant-select-selection-placeholder {
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.job-statuses-source-select .ant-select-selector {
|
|
|
|
|
align-items: flex-start !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.job-statuses-source-select .ant-select-selection-wrap {
|
|
|
|
|
gap: 4px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.job-statuses-source-tag-wrapper {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
margin-inline-end: 4px;
|
|
|
|
|
touch-action: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.job-statuses-source-tag-wrapper .ant-select-selection-item {
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
cursor: grab;
|
|
|
|
|
margin-inline-end: 0;
|
|
|
|
|
user-select: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.job-statuses-source-tag-wrapper .ant-select-selection-item-content {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.job-statuses-source-tag-wrapper .ant-select-selection-item:active {
|
|
|
|
|
cursor: grabbing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.job-statuses-source-tag-wrapper .ant-select-selection-item-remove {
|
|
|
|
|
flex: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.job-statuses-source-tag-wrapper--dragging {
|
|
|
|
|
opacity: 0.55;
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const normalizeStatuses = (statuses) => [...new Set((statuses || []).map((item) => item?.trim()).filter(Boolean))];
|
|
|
|
|
|
|
|
|
|
const DraggableStatusTag = ({ label, value, closable, onClose }) => {
|
|
|
|
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
|
|
|
id: value
|
|
|
|
|
});
|
|
|
|
|
const labelText = String(label ?? value);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<span
|
|
|
|
|
ref={setNodeRef}
|
|
|
|
|
className={`job-statuses-source-tag-wrapper ${isDragging ? "job-statuses-source-tag-wrapper--dragging" : ""}`}
|
|
|
|
|
style={{ transform: CSS.Transform.toString(transform), transition }}
|
|
|
|
|
onPointerDownCapture={(event) => {
|
|
|
|
|
if (event.target.closest(".ant-tag-close-icon")) {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
{...attributes}
|
|
|
|
|
{...listeners}
|
|
|
|
|
>
|
|
|
|
|
<span
|
|
|
|
|
className="ant-select-selection-item"
|
|
|
|
|
onMouseDown={(event) => {
|
|
|
|
|
if (event.target.closest(".ant-select-selection-item-remove")) {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}}
|
|
|
|
|
title={labelText}
|
|
|
|
|
>
|
|
|
|
|
<span className="ant-select-selection-item-content">{labelText}</span>
|
|
|
|
|
{closable ? (
|
|
|
|
|
<span
|
|
|
|
|
className="ant-select-selection-item-remove"
|
|
|
|
|
onClick={(event) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
onClose?.(event);
|
|
|
|
|
}}
|
|
|
|
|
onMouseDown={(event) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<CloseOutlined />
|
|
|
|
|
</span>
|
|
|
|
|
) : null}
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const SortableStatusesSelect = ({ value, onChange }) => {
|
|
|
|
|
const statuses = normalizeStatuses(value);
|
|
|
|
|
const tagSensors = useSensors(
|
|
|
|
|
useSensor(PointerSensor, {
|
|
|
|
|
activationConstraint: {
|
|
|
|
|
distance: 6
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleStatusesChange = (nextValues) => {
|
|
|
|
|
onChange?.(normalizeStatuses(nextValues));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleStatusSortEnd = ({ active, over }) => {
|
|
|
|
|
if (!over || active.id === over.id) return;
|
|
|
|
|
|
|
|
|
|
const oldIndex = statuses.indexOf(active.id);
|
|
|
|
|
const newIndex = statuses.indexOf(over.id);
|
|
|
|
|
|
|
|
|
|
if (oldIndex < 0 || newIndex < 0) return;
|
|
|
|
|
|
|
|
|
|
onChange?.(arrayMove(statuses, oldIndex, newIndex));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const renderStatusTag = ({ label, value: tagValue, closable, onClose }) => {
|
|
|
|
|
return <DraggableStatusTag closable={closable} label={label} onClose={onClose} value={tagValue} />;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<DndContext collisionDetection={closestCenter} onDragEnd={handleStatusSortEnd} sensors={tagSensors}>
|
|
|
|
|
<SortableContext items={statuses} strategy={rectSortingStrategy}>
|
|
|
|
|
<Select
|
|
|
|
|
className="job-statuses-source-select"
|
|
|
|
|
mode="tags"
|
|
|
|
|
onChange={handleStatusesChange}
|
|
|
|
|
tagRender={renderStatusTag}
|
|
|
|
|
value={statuses}
|
|
|
|
|
/>
|
|
|
|
|
</SortableContext>
|
|
|
|
|
</DndContext>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
const statusOptions = Form.useWatch(["md_ro_statuses", "statuses"], form) || [];
|
|
|
|
|
const allStatuses = normalizeStatuses(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 statusOptions = allStatuses;
|
|
|
|
|
const statusSelectOptions = statusOptions.map((item) => ({ value: item, label: item }));
|
|
|
|
|
const availableProductionStatuses = [...new Set([...productionStatuses, ...additionalBoardStatuses].filter(Boolean))];
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
@@ -69,13 +210,21 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
label={t("bodyshop.labels.alljobstatuses")}
|
|
|
|
|
rules={[
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
//message: t("general.validation.required"),
|
|
|
|
|
type: "array"
|
|
|
|
|
validator: async (_, value) => {
|
|
|
|
|
const populatedStatuses = normalizeStatuses(value);
|
|
|
|
|
|
|
|
|
|
if (populatedStatuses.length === 0) {
|
|
|
|
|
return Promise.reject(new Error(t("general.validation.required")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (populatedStatuses.length !== (value || []).filter(Boolean).length) {
|
|
|
|
|
return Promise.reject(new Error(t("bodyshop.errors.duplicate_job_status")));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Select mode="tags" />
|
|
|
|
|
<SortableStatusesSelect />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name={["md_ro_statuses", "active_statuses"]}
|
|
|
|
|
@@ -88,7 +237,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select mode="multiple" options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name={["md_ro_statuses", "pre_production_statuses"]}
|
|
|
|
|
@@ -101,7 +250,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select mode="multiple" options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name={["md_ro_statuses", "production_statuses"]}
|
|
|
|
|
@@ -114,7 +263,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select mode="multiple" options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name={["md_ro_statuses", "post_production_statuses"]}
|
|
|
|
|
@@ -127,7 +276,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select mode="multiple" options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name={["md_ro_statuses", "ready_statuses"]}
|
|
|
|
|
@@ -140,7 +289,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select mode="multiple" options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name={["md_ro_statuses", "additional_board_statuses"]}
|
|
|
|
|
@@ -153,7 +302,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<Select mode="multiple" options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select mode="multiple" options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</div>
|
|
|
|
|
</LayoutFormRow>
|
|
|
|
|
@@ -168,7 +317,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
]}
|
|
|
|
|
name={["md_ro_statuses", "default_scheduled"]}
|
|
|
|
|
>
|
|
|
|
|
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
label={t("bodyshop.fields.statuses.default_arrived")}
|
|
|
|
|
@@ -180,7 +329,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
]}
|
|
|
|
|
name={["md_ro_statuses", "default_arrived"]}
|
|
|
|
|
>
|
|
|
|
|
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
label={t("bodyshop.fields.statuses.default_exported")}
|
|
|
|
|
@@ -192,7 +341,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
]}
|
|
|
|
|
name={["md_ro_statuses", "default_exported"]}
|
|
|
|
|
>
|
|
|
|
|
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
label={t("bodyshop.fields.statuses.default_imported")}
|
|
|
|
|
@@ -204,7 +353,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
]}
|
|
|
|
|
name={["md_ro_statuses", "default_imported"]}
|
|
|
|
|
>
|
|
|
|
|
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
label={t("bodyshop.fields.statuses.default_invoiced")}
|
|
|
|
|
@@ -216,7 +365,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
]}
|
|
|
|
|
name={["md_ro_statuses", "default_invoiced"]}
|
|
|
|
|
>
|
|
|
|
|
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
label={t("bodyshop.fields.statuses.default_completed")}
|
|
|
|
|
@@ -228,7 +377,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
]}
|
|
|
|
|
name={["md_ro_statuses", "default_completed"]}
|
|
|
|
|
>
|
|
|
|
|
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
label={t("bodyshop.fields.statuses.default_delivered")}
|
|
|
|
|
@@ -240,7 +389,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
]}
|
|
|
|
|
name={["md_ro_statuses", "default_delivered"]}
|
|
|
|
|
>
|
|
|
|
|
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item
|
|
|
|
|
label={t("bodyshop.fields.statuses.default_void")}
|
|
|
|
|
@@ -252,7 +401,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
|
|
|
]}
|
|
|
|
|
name={["md_ro_statuses", "default_void"]}
|
|
|
|
|
>
|
|
|
|
|
<Select options={statusOptions.map((item) => ({ value: item, label: item }))} />
|
|
|
|
|
<Select options={statusSelectOptions} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</LayoutFormRow>
|
|
|
|
|
{Production_List_Status_Colors.treatment === "on" && (
|
|
|
|
|
|