'use client'; /** * NOVA CMS - CONTENT ENTRY LIST * ============================== * * Lista de entradas de contenido para un ContentType específico. * Permite ver, editar y eliminar entradas del headless CMS. * * CARACTERÍSTICAS: * - Vista de tabla/grid responsiva * - Filtrado y búsqueda * - Acciones CRUD (Create, Read, Update, Delete) * - Paginación * - Vista previa de contenido */ import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Search, Plus, MoreHorizontal, Edit, Trash2, Eye, Calendar, FileText } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; interface ContentEntry { id: string; data: Record; createdAt: Date; updatedAt: Date; contentType: { id: string; name: string; apiIdentifier: string; fields: Array<{ id: string; label: string; apiIdentifier: string; type: string; }>; }; } interface ContentEntryListProps { contentTypeId: string; onCreateNew?: () => void; onEdit?: (entryId: string) => void; onView?: (entryId: string) => void; onDelete?: (entryId: string) => void; } export function ContentEntryList({ contentTypeId, onCreateNew, onEdit, onView, onDelete }: ContentEntryListProps) { const { toast } = useToast(); const [entries, setEntries] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [contentType, setContentType] = useState(null); useEffect(() => { fetchEntries(); }, [contentTypeId]); const fetchEntries = async () => { try { setLoading(true); const response = await fetch(`/api/content/${contentTypeId}/entries`); const data = await response.json(); if (response.ok) { setEntries(data.entries || []); setContentType(data.contentType); } else { toast({ variant: 'destructive', title: 'Error', description: 'No se pudieron cargar las entradas.', }); } } catch (error) { toast({ variant: 'destructive', title: 'Error', description: 'Error de conexión.', }); } finally { setLoading(false); } }; const handleDelete = async (entryId: string) => { if (!confirm('¿Estás seguro de que quieres eliminar esta entrada?')) { return; } try { const response = await fetch(`/api/content/entries/${entryId}`, { method: 'DELETE', }); if (response.ok) { setEntries(entries.filter(entry => entry.id !== entryId)); toast({ title: 'Eliminado', description: 'La entrada se eliminó exitosamente.', }); if (onDelete) onDelete(entryId); } else { throw new Error('Error al eliminar'); } } catch (error) { toast({ variant: 'destructive', title: 'Error', description: 'No se pudo eliminar la entrada.', }); } }; const filteredEntries = entries.filter(entry => { if (!searchTerm) return true; // Buscar en todos los campos de datos const searchContent = Object.values(entry.data).join(' ').toLowerCase(); return searchContent.includes(searchTerm.toLowerCase()); }); const formatFieldValue = (value: any, fieldType: string) => { if (value === null || value === undefined) return '-'; switch (fieldType) { case 'BOOLEAN': return value ? '✅ Sí' : '❌ No'; case 'DATE': return new Date(value).toLocaleDateString(); case 'NUMBER': return value.toLocaleString(); case 'MEDIA': return value ? '🖼️ Media' : '-'; default: return String(value).length > 50 ? String(value).substring(0, 50) + '...' : String(value); } }; const getDisplayFields = () => { if (!contentType?.fields) return []; // Mostrar máximo 3 campos principales en la tabla return contentType.fields.slice(0, 3); }; if (loading) { return (
); } return (
{/* Header */}

{contentType?.name || 'Contenido'}

{entries.length} entradas encontradas

{/* Search */}
setSearchTerm(e.target.value)} className="pl-10" />
{/* Content List */} {filteredEntries.length === 0 ? (

No hay contenido

{searchTerm ? 'No se encontraron entradas que coincidan con tu búsqueda.' : 'Aún no se ha creado contenido de este tipo.' }

{!searchTerm && ( )}
) : ( {getDisplayFields().map((field: any) => ( {field.label} ))} Acciones {filteredEntries.map((entry) => ( {getDisplayFields().map((field: any) => ( {formatFieldValue( entry.data[field.apiIdentifier], field.type )} ))}
{new Date(entry.updatedAt).toLocaleDateString()}
onView?.(entry.id)}> Ver onEdit?.(entry.id)}> Editar handleDelete(entry.id)} className="text-red-600" > Eliminar
))}
)}
); }