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 ( + + + + - - - - - - @@ -168,7 +317,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) { ]} name={["md_ro_statuses", "default_scheduled"]} > - - - - - - - - {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": "",