// * Packages import { CSS } from "@dnd-kit/utilities"; import { GripVerticalIcon, TrashIcon } from "lucide-react"; import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; // * Components shadcn import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; import { Card, CardContent } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; // * Other import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import AddNewFieldBtn from "@/back/Components/PageConfigs/AddNewFieldBtn"; import DynamicInputs from "@/back/Components/DynamicPages/DynamicInputs"; import useTrans from "@/hooks/use-translations"; import { extractMinMaxFromValidation, extractRequiredValidation } from "@/hooks/use-laravel-validation"; import { ConfigField, DynamicInternalField } from "@/back/types/global"; type DynamicListProps = { name: string, parentName: string, value: DynamicInternalField[], enumLabel: string, field: ConfigField, content: DynamicInternalField | null; setData: (name: string, value: string | boolean | File | object | null) => void; reset: (name?: string) => void; } export default function DynamicList({ name, parentName, value, enumLabel, field, content, setData, reset, }: DynamicListProps) { // Notice user there must be children config. if (!field.children && field.children!.length > 0) { return (

Des enfants sont nécessaires pour ce type {enumLabel}

); } // Drag&Drop. const sensors = useSensors(useSensor(PointerSensor)); // Set local id. const localValue = value.map((elmt, index) => { return { ...elmt, id: elmt.id ?? index + 1 }; }); const sortedListItems = [...localValue].sort((a, b) => a.order - b.order); const isRequired = extractRequiredValidation(field.validation); const configLimitMax = extractMinMaxFromValidation(field.validation).max ?? 1; const removeIds = (fields: DynamicInternalField[]): Omit[] => { return fields.map(({ id, ...rest }) => { const newObj: any = {}; for (const key in rest) { const value = rest[key as keyof typeof rest]; // Si c’est un tableau, on le traite récursivement if (Array.isArray(value)) { newObj[key] = removeIds(value); } // Sinon, on garde la valeur telle quelle else { newObj[key] = value; } } return newObj; }); }; return (
{value.length > 0 && ( { const { active, over } = event; if (!over || active.id === over.id) return; const oldIndex = sortedListItems.findIndex((i) => i.id === active.id); const newIndex = sortedListItems.findIndex((i) => i.id === over.id); const reorderedList = arrayMove(sortedListItems, oldIndex, newIndex) .map((item, index) => ({ ...item, order: index + 1, })); setData(`${parentName}.${field.key}`, reorderedList); }} > item.id)} strategy={verticalListSortingStrategy} > {sortedListItems.map((item, itemIndex) => ( { const list = Array.isArray(content?.[fieldKey as keyof typeof content]) // @ts-ignore ? [...content[fieldKey as keyof typeof content]] : []; list.splice(index, 1); setData(`${parentName}.${fieldKey}`, list); }} /> ))} )} { const newItem = Object.fromEntries( (field.children ?? []).map(child => [child.key, ""]) ); const maxOrder = Math.max(0, ...sortedListItems.map((field) => field.order ?? 0)) + 1; const maxIndex = Math.max(0, ...sortedListItems.map((field) => field.id ?? 0)) + 1; const newIndex = value.length; const newItemWithOrder = { ...newItem, id: maxIndex, order: maxOrder }; setData(`${parentName}.${field.key}.${newIndex}`, newItemWithOrder); }} />
) } type SortableItemProps = { id: number; item: any; itemIndex: number; name: string; parentName: string; field: any; setData: (name: string, value: string | boolean | File | object | null) => void; reset: (name?: string) => void; removeField: (parentName: string, fieldKey: string, index: number) => void; } function SortableItem({ id, item, itemIndex, name, parentName, field, setData, reset, removeField, }: SortableItemProps) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id }); const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, zIndex: isDragging ? 50 : 0, }; // Translations. const transDragAndDrop = useTrans("back.dnd_field"); const transDelete = useTrans("back.delete_field"); return (
{/* Drag handle */} } tooltipContent={transDragAndDrop} className="cursor-grab h-full w-full" type="button" variant="outline" size="icon" {...listeners} {...attributes} />
} tooltipContent={transDelete} type="button" variant="destructive" size="icon" onClick={() => removeField(parentName, field.key, itemIndex)} />
Élément n°{itemIndex + 1}
); }