'use client'; import { useFormContext, useFieldArray, useWatch } from 'react-hook-form'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Switch } from "@/components/ui/switch"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Text, Pilcrow, Hash, ToggleRight, Calendar, ImageIcon, GripVertical, Trash2 } from 'lucide-react'; import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, DragEndEvent, DragOverlay, useDraggable, useDroppable, DragOverEvent } from '@dnd-kit/core'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { toCamelCase } from '@/utils/formatters'; import { ContentTypeFormValues, fieldSchema } from './ContentTypeForm'; import * as z from 'zod'; const fieldTypes: { type: z.infer['type']; label: string; icon: JSX.Element }[] = [ { type: 'TEXT', label: 'Texto Corto', icon: }, { type: 'RICH_TEXT', label: 'Texto Largo', icon: }, { type: 'NUMBER', label: 'Número', icon: }, { type: 'BOOLEAN', label: 'Sí/No', icon: }, { type: 'DATE', label: 'Fecha', icon: }, { type: 'MEDIA', label: 'Media', icon: }, ]; function PaletteItem({ fieldType }: { fieldType: typeof fieldTypes[0] }) { const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id: `palette-${fieldType.type}`, data: { type: 'palette', fieldType: fieldType.type } }); return ( ); } function SortableFieldRow({ field, index, remove }: { field: any; index: number; remove: (index: number) => void; }) { const { control, setValue, getValues } = useFormContext(); const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: field.id, data: { type: 'field', field, index } }); const style = { transform: CSS.Transform.toString(transform), transition: transition || 'transform 250ms ease', zIndex: isDragging ? 1000 : 1 }; const labelValue = useWatch({ control, name: `fields.${index}.label` }); useEffect(() => { if (labelValue) { setValue(`fields.${index}.apiIdentifier`, toCamelCase(labelValue)); } }, [labelValue, index, setValue]); return (
( )} /> ( )} /> (
{switchField.value ? 'Requerido' : 'Opcional'}
)} />
); } const DropPlaceholder = ({ isOver, index }: { isOver: boolean; index?: number }) => { const { setNodeRef } = useDroppable({ id: `drop-placeholder-${index || 0}`, data: { type: 'placeholder', index: index || 0 } }); return (
{isOver ? (

Suelta aquí para añadir campo

) : (

Zona de drop

)}
); }; export function FieldsBuilder() { const { control, setValue, getValues } = useFormContext(); const { fields, append, remove, move } = useFieldArray({ control, name: "fields" }); const [activeId, setActiveId] = useState(null); const [overId, setOverId] = useState(null); const [isMounted, setIsMounted] = useState(false); useEffect(() => { setIsMounted(true); }, []); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8 } }) ); const handleDragStart = (event: DragEndEvent) => { console.log('🟡 Drag Start:', event.active.id); setActiveId(event.active.id as string); }; const handleDragOver = (event: DragOverEvent) => { const overId = event.over?.id as string | null; console.log('🔵 Drag Over:', { activeId: event.active.id, overId }); setOverId(overId); }; const handleDragCancel = () => { console.log('🔴 Drag Cancel'); setActiveId(null); setOverId(null); }; const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; console.log('🟢 Drag End:', { activeId: active.id, overId: over?.id }); setActiveId(null); setOverId(null); if (!over) { console.log('❌ No drop target'); return; } const activeIdStr = active.id.toString(); const overIdStr = over.id.toString(); // Si arrastramos desde la paleta if (activeIdStr.startsWith('palette-')) { const fieldType = activeIdStr.replace('palette-', ''); const newLabel = `Campo ${fieldType.toLowerCase()}`; const newField = { id: `field-${Date.now()}`, label: newLabel, apiIdentifier: toCamelCase(newLabel), type: fieldType as any, isRequired: false }; console.log('➕ Adding new field:', newField); // Si se suelta en un placeholder if (overIdStr.startsWith('drop-placeholder-')) { const insertIndex = parseInt(overIdStr.replace('drop-placeholder-', ''), 10); const currentFields = getValues('fields') || []; const newFields = [...currentFields]; newFields.splice(insertIndex, 0, newField); setValue('fields', newFields, { shouldDirty: true }); console.log('✅ Field added at index:', insertIndex); return; } // Si se suelta sobre un campo existente, insertamos antes const overIndex = fields.findIndex((f) => f.id === overIdStr); if (overIndex !== -1) { const currentFields = getValues('fields') || []; const newFields = [...currentFields]; newFields.splice(overIndex, 0, newField); setValue('fields', newFields, { shouldDirty: true }); console.log('✅ Field added before existing field at index:', overIndex); return; } // Si no hay target específico, añadir al final append(newField); console.log('✅ Field added at end'); return; } // Si reorganizamos campos existentes if (activeIdStr !== overIdStr) { const oldIndex = fields.findIndex((f) => f.id === activeIdStr); const newIndex = fields.findIndex((f) => f.id === overIdStr); if (oldIndex !== -1 && newIndex !== -1) { console.log('🔄 Moving field from', oldIndex, 'to', newIndex); move(oldIndex, newIndex); } } }; const activeItem = activeId ? ( fieldTypes.find(f => `palette-${f.type}` === activeId) || fields.find(f => f.id === activeId) ) : null; return (
Estructura de Contenido Arrastra campos desde la paleta y reordénalos según necesites.
{/* Drop zone inicial */} {fields.map((field, index) => (
{/* Drop zone después de cada campo */}
))}
{fields.length === 0 && !activeId && (

Arrastra un campo desde la paleta para empezar

Los campos aparecerán aquí y podrás reordenarlos

)}
Paleta de Campos
Arrastra estos campos al constructor
{fieldTypes.map(ft => ( ))}
{/* Drag overlay */} {isMounted && createPortal( {activeId && activeItem ? (
{(activeItem as any).icon || '📝'} {activeItem.label}
) : null}
, document.body )}
); }