diff --git a/client/src/components/shop-info/shop-info.rostatus.component.jsx b/client/src/components/shop-info/shop-info.rostatus.component.jsx
index bad3b3fd3..273d04bae 100644
--- a/client/src/components/shop-info/shop-info.rostatus.component.jsx
+++ b/client/src/components/shop-info/shop-info.rostatus.component.jsx
@@ -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 (
+ {
+ if (event.target.closest(".ant-tag-close-icon")) {
+ event.stopPropagation();
+ }
+ }}
+ {...attributes}
+ {...listeners}
+ >
+ {
+ if (event.target.closest(".ant-select-selection-item-remove")) {
+ event.stopPropagation();
+ return;
+ }
+
+ event.preventDefault();
+ }}
+ title={labelText}
+ >
+ {labelText}
+ {closable ? (
+ {
+ event.stopPropagation();
+ onClose?.(event);
+ }}
+ onMouseDown={(event) => {
+ event.stopPropagation();
+ }}
+ >
+
+
+ ) : null}
+
+
+ );
+};
+
+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 ;
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
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")));
+ }
+ }
}
]}
>
-
+
-
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
@@ -168,7 +317,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
]}
name={["md_ro_statuses", "default_scheduled"]}
>
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
- ({ value: item, label: item }))} />
+
{Production_List_Status_Colors.treatment === "on" && (
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 64e4665c8..d2584bea0 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -306,6 +306,7 @@
},
"errors": {
"creatingdefaultview": "Error creating default view.",
+ "duplicate_job_status": "Duplicate job status. Each job status must be unique.",
"duplicate_insurance_company": "Duplicate insurance company name. Each insurance company name must be unique",
"loading": "Unable to load shop details. Please call technical support.",
"saving": "Error encountered while saving. {{message}}",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index a61fe6612..0fd4e9622 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -306,6 +306,7 @@
},
"errors": {
"creatingdefaultview": "",
+ "duplicate_job_status": "",
"duplicate_insurance_company": "",
"loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico.",
"saving": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index 1520127ff..11b4fdc7f 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -306,6 +306,7 @@
},
"errors": {
"creatingdefaultview": "",
+ "duplicate_job_status": "",
"duplicate_insurance_company": "",
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.",
"saving": "",