// * 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}
);
}