'use client'; import React, { useCallback, useEffect, useState } from 'react'; import { History, RefreshCw, User, Calendar, FileText, ChevronDown, ChevronRight, Filter, } from 'lucide-react'; import { PageHeader, Surface, EmptyState } from '@/components/ui'; type Row = { id: string; tenant_id: string | null; tenant_name: string | null; user_email: string; user_role: string | null; entity_type: string; entity_id: string | null; action: string; summary: string; detail: Record; source: string | null; created_at: string; }; const ENTITY_LABELS: Record = { brandbook: 'Zona de marca', creative: 'Creatividad', calendar: 'Calendario', tenant: 'Cliente', document: 'Documento', enrichment: 'Enriquecimiento', }; function fmtDate(iso: string): string { try { return new Intl.DateTimeFormat('es-ES', { dateStyle: 'medium', timeStyle: 'short', }).format(new Date(iso)); } catch { return iso; } } function entityLabel(t: string): string { return ENTITY_LABELS[t] || t; } function DetailBlock({ detail }: { detail: Record }) { const changes = Array.isArray(detail.changes) ? (detail.changes as { field?: string; from?: unknown; to?: unknown }[]) : null; const fields = Array.isArray(detail.filled_fields) ? (detail.filled_fields as string[]) : null; const sources = Array.isArray(detail.sources) ? (detail.sources as string[]) : null; if (changes?.length) { return (
    {changes.map((c, i) => (
  • {c.field}
    {c.from == null || c.from === '' ? '∅' : typeof c.from === 'string' ? c.from : JSON.stringify(c.from)} {c.to == null || c.to === '' ? '∅' : typeof c.to === 'string' ? c.to : JSON.stringify(c.to)}
  • ))}
); } if (fields?.length || sources?.length) { return (
{sources?.length ? (

Fuentes:{' '} {sources.join(', ')}

) : null} {fields?.length ? (

Campos:{' '} {fields.join(', ')}

) : null} {typeof detail.note === 'string' ? (

{detail.note}

) : null}
); } const keys = Object.keys(detail || {}); if (!keys.length) { return

Sin detalle adicional.

; } return (
      {JSON.stringify(detail, null, 2)}
    
); } export default function ChangeHistoryView({ isAgency, initialTenantId, }: { isAgency: boolean; initialTenantId?: string | null; }) { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [needsMigration, setNeedsMigration] = useState(false); const [entityType, setEntityType] = useState(''); const [portfolioWide, setPortfolioWide] = useState(false); const [openId, setOpenId] = useState(null); const load = useCallback(async () => { setLoading(true); setError(null); try { const q = new URLSearchParams(); q.set('limit', '80'); if (entityType) q.set('entity_type', entityType); if (initialTenantId) q.set('tenant_id', initialTenantId); const r = await fetch(`/api/change-history?${q}`, { cache: 'no-store' }); const j = await r.json(); if (!r.ok || j.ok === false) { setNeedsMigration(Boolean(j.needsMigration)); setError(j.error || 'No se pudo cargar el historial'); setRows([]); return; } setNeedsMigration(false); setRows(j.rows || []); setPortfolioWide(Boolean(j.portfolio_wide)); } catch (e) { setError(e instanceof Error ? e.message : 'Error de red'); setRows([]); } finally { setLoading(false); } }, [entityType, initialTenantId]); useEffect(() => { void load(); }, [load]); return (
void load()} className="inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50" > Actualizar } /> {isAgency ? (

{portfolioWide ? 'Vista portafolio (todos los clientes)' : 'Filtrado por el cliente del contexto'}

) : null}
{needsMigration ? (

Falta aplicar la migración SQL{' '} sql/change_history.sql {' '} en Supabase. Hasta entonces no se pueden guardar ni listar cambios.

) : null} {error && !needsMigration ? (

{error}

) : null} {loading && !rows.length ? (
Cargando historial…
) : !rows.length ? (
) : (
    {rows.map((row) => { const open = openId === row.id; return (
  • {open ? (
    {row.source ? (

    Origen: {row.source}

    ) : null}
    ) : null}
  • ); })}
)}
); }