@anthonybir/birhaus-patterns
Version:
BIRHAUS Pattern Components - AuditTimeline, DataTable, EmptyState
1 lines • 21.4 kB
Source Map (JSON)
{"version":3,"sources":["../src/components/AuditTimeline.tsx"],"names":["cn","classes","VERB_DESCRIPTIONS","ACTION_COLORS","AuditTimeline","events","showFilters","maxHeight","className","emptyMessage","filterAllEntitiesLabel","filterAllActionsLabel","clearFiltersLabel","showDetailsLabel","hideDetailsLabel","filterEntity","setFilterEntity","useState","filterVerb","setFilterVerb","expandedEvents","setExpandedEvents","entities","verbs","useMemo","uniqueEntities","uniqueVerbs","event","filteredEvents","toggleExpanded","eventId","newExpanded","getActionColor","verb","lastPart","formatPayload","payload","jsx","key","value","jsxs","Filter","entity","Clock","index","isExpanded","hasPayload","User","Fragment","ChevronUp","ChevronDown","formatDistanceToNow","es","useAuditTimeline","setEvents","newEvent","prev"],"mappings":"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","file":"index.mjs","sourcesContent":["'use client'\n\nimport { useState, useMemo } from 'react'\nimport { Clock, User, Filter, ChevronDown, ChevronUp } from 'lucide-react'\nimport { formatDistanceToNow } from 'date-fns'\nimport { es } from 'date-fns/locale'\n\n/**\n * BIRHAUS AuditTimeline\n * \n * Implements BIRHAUS principle #10: \"Auditability\"\n * - Complete audit trail with Spanish-first descriptions\n * - Progressive disclosure of event details \n * - Filterable timeline with real-time search\n * - Color-coded actions for quick visual scanning\n * - Expandable payload details for technical users\n * - Automatic relative time formatting in Spanish\n */\n\n// BIRHAUS: Utility function (replaces @/lib/utils cn)\nfunction cn(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(' ')\n}\n\nexport type AuditEvent = {\n id: string\n actor_id: string\n actor_name?: string\n verb: string\n entity: string | null\n entity_id: string | null\n payload: Record<string, any>\n request_id: string | null\n created_at: string\n}\n\ntype AuditTimelineProps = {\n events: AuditEvent[]\n showFilters?: boolean\n maxHeight?: string\n className?: string\n // BIRHAUS: Spanish-first customization\n emptyMessage?: string\n filterAllEntitiesLabel?: string\n filterAllActionsLabel?: string\n clearFiltersLabel?: string\n showDetailsLabel?: string\n hideDetailsLabel?: string\n}\n\n// BIRHAUS: Spanish-first verb descriptions\nconst VERB_DESCRIPTIONS: Record<string, string> = {\n 'iglesia.creada': 'creó una iglesia',\n 'iglesia.actualizada': 'actualizó una iglesia',\n 'iglesia.eliminada': 'eliminó una iglesia',\n 'pastor.creado': 'registró un pastor',\n 'pastor.actualizado': 'actualizó un pastor',\n 'pastor.eliminado': 'eliminó un pastor',\n 'donacion.registrada': 'registró una donación',\n 'donacion.anulada': 'anuló una donación',\n 'transferencia.creada': 'creó una transferencia',\n 'transferencia.completada': 'completó una transferencia',\n 'gasto.registrado': 'registró un gasto',\n 'gasto.aprobado': 'aprobó un gasto',\n 'gasto.rechazado': 'rechazó un gasto',\n 'usuario.login': 'inició sesión',\n 'usuario.logout': 'cerró sesión',\n 'resumen.enviado': 'envió un resumen',\n 'resumen.aprobado': 'aprobó un resumen',\n 'resumen.rechazado': 'rechazó un resumen',\n // Generic fallbacks\n 'create': 'creó',\n 'update': 'actualizó', \n 'delete': 'eliminó',\n 'approve': 'aprobó',\n 'reject': 'rechazó',\n 'login': 'inició sesión',\n 'logout': 'cerró sesión'\n}\n\n// BIRHAUS: Semantic color coding for actions\nconst ACTION_COLORS: Record<string, string> = {\n creada: 'text-green-600',\n creado: 'text-green-600',\n registrada: 'text-green-600',\n registrado: 'text-green-600',\n actualizada: 'text-blue-600',\n actualizado: 'text-blue-600',\n eliminada: 'text-red-600',\n eliminado: 'text-red-600',\n anulada: 'text-yellow-600',\n aprobado: 'text-green-600',\n rechazado: 'text-red-600',\n completada: 'text-green-600',\n enviado: 'text-blue-600',\n login: 'text-gray-500',\n logout: 'text-gray-500',\n}\n\nexport function AuditTimeline({\n events,\n showFilters = true,\n maxHeight = '600px',\n className = '',\n emptyMessage = 'No hay eventos que mostrar',\n filterAllEntitiesLabel = 'Todas las entidades',\n filterAllActionsLabel = 'Todas las acciones',\n clearFiltersLabel = 'Limpiar filtros',\n showDetailsLabel = 'Ver detalles',\n hideDetailsLabel = 'Ocultar detalles'\n}: AuditTimelineProps) {\n const [filterEntity, setFilterEntity] = useState<string>('all')\n const [filterVerb, setFilterVerb] = useState<string>('all')\n const [expandedEvents, setExpandedEvents] = useState<Set<string>>(new Set())\n\n // BIRHAUS: Extract unique entities and verbs for progressive filtering\n const { entities, verbs } = useMemo(() => {\n const uniqueEntities = new Set<string>()\n const uniqueVerbs = new Set<string>()\n \n events.forEach(event => {\n if (event.entity) uniqueEntities.add(event.entity)\n uniqueVerbs.add(event.verb)\n })\n \n return {\n entities: Array.from(uniqueEntities).sort(),\n verbs: Array.from(uniqueVerbs).sort()\n }\n }, [events])\n\n // Filter events based on selected criteria\n const filteredEvents = useMemo(() => {\n return events.filter(event => {\n if (filterEntity !== 'all' && event.entity !== filterEntity) return false\n if (filterVerb !== 'all' && event.verb !== filterVerb) return false\n return true\n })\n }, [events, filterEntity, filterVerb])\n\n // Toggle expanded state for event details\n const toggleExpanded = (eventId: string) => {\n const newExpanded = new Set(expandedEvents)\n if (newExpanded.has(eventId)) {\n newExpanded.delete(eventId)\n } else {\n newExpanded.add(eventId)\n }\n setExpandedEvents(newExpanded)\n }\n\n // Get semantic color based on verb action\n const getActionColor = (verb: string) => {\n const lastPart = verb.split('.').pop() || ''\n return ACTION_COLORS[lastPart] || 'text-gray-700'\n }\n\n // BIRHAUS: Format payload with progressive disclosure\n const formatPayload = (payload: Record<string, any>) => {\n if (!payload || Object.keys(payload).length === 0) return null\n \n return (\n <div className=\"mt-2 p-3 bg-gray-50 rounded-md text-xs space-y-1 border\">\n {Object.entries(payload).map(([key, value]) => (\n <div key={key} className=\"flex gap-2\">\n <span className=\"text-gray-500 font-medium min-w-20\">{key}:</span>\n <span className=\"text-gray-700 flex-1\">\n {typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value)}\n </span>\n </div>\n ))}\n </div>\n )\n }\n\n return (\n <div className={cn('space-y-4', className)} data-birhaus-component=\"audit-timeline\">\n {/* BIRHAUS: Progressive disclosure filters */}\n {showFilters && (entities.length > 0 || verbs.length > 0) && (\n <div className=\"flex gap-3 items-center p-3 bg-gray-50 border border-gray-200 rounded-md\">\n <Filter className=\"h-4 w-4 text-gray-400\" />\n \n {entities.length > 0 && (\n <select\n value={filterEntity}\n onChange={(e) => setFilterEntity(e.target.value)}\n className=\"px-3 py-1 border border-gray-300 rounded-md text-sm bg-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500\"\n >\n <option value=\"all\">{filterAllEntitiesLabel}</option>\n {entities.map(entity => (\n <option key={entity} value={entity}>\n {entity.charAt(0).toUpperCase() + entity.slice(1)}\n </option>\n ))}\n </select>\n )}\n \n {verbs.length > 0 && (\n <select\n value={filterVerb}\n onChange={(e) => setFilterVerb(e.target.value)}\n className=\"px-3 py-1 border border-gray-300 rounded-md text-sm bg-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500\"\n >\n <option value=\"all\">{filterAllActionsLabel}</option>\n {verbs.map(verb => (\n <option key={verb} value={verb}>\n {VERB_DESCRIPTIONS[verb] || verb}\n </option>\n ))}\n </select>\n )}\n \n {(filterEntity !== 'all' || filterVerb !== 'all') && (\n <button\n onClick={() => {\n setFilterEntity('all')\n setFilterVerb('all')\n }}\n className=\"text-sm text-primary-600 hover:text-primary-700 hover:underline focus:outline-none focus:ring-2 focus:ring-primary-500 px-2 py-1 rounded\"\n >\n {clearFiltersLabel}\n </button>\n )}\n </div>\n )}\n\n {/* BIRHAUS: Audit timeline with visual hierarchy */}\n <div \n className=\"space-y-2 overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100\"\n style={{ maxHeight }}\n >\n {filteredEvents.length === 0 ? (\n <div className=\"text-center py-8 text-gray-500 bg-gray-50 rounded-lg border-2 border-dashed border-gray-200\">\n <Clock className=\"h-8 w-8 text-gray-400 mx-auto mb-2\" />\n <p>{emptyMessage}</p>\n </div>\n ) : (\n filteredEvents.map((event, index) => {\n const isExpanded = expandedEvents.has(event.id)\n const hasPayload = event.payload && Object.keys(event.payload).length > 0\n \n return (\n <div\n key={event.id}\n className=\"flex gap-3 p-4 bg-white border border-gray-200 rounded-lg hover:border-gray-300 hover:shadow-sm transition-all duration-150\"\n >\n {/* BIRHAUS: Visual timeline indicator */}\n <div className=\"flex flex-col items-center\">\n <div className=\"w-2 h-2 rounded-full bg-primary-600 mt-2 flex-shrink-0\" />\n {index < filteredEvents.length - 1 && (\n <div className=\"w-px flex-1 bg-gray-200 mt-2\" />\n )}\n </div>\n\n {/* Event content */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <User className=\"h-3 w-3 text-gray-400\" />\n <span className=\"text-sm font-medium text-gray-900\">\n {event.actor_name || 'Usuario'}\n </span>\n <span className={cn('text-sm font-medium', getActionColor(event.verb))}>\n {VERB_DESCRIPTIONS[event.verb] || event.verb}\n </span>\n {event.entity && (\n <span className=\"text-xs px-2 py-1 bg-gray-200 text-gray-700 rounded-full\">\n {event.entity}\n </span>\n )}\n </div>\n \n {event.entity_id && (\n <div className=\"text-xs text-gray-500 mt-1 font-mono\">\n ID: {event.entity_id}\n </div>\n )}\n \n {/* BIRHAUS: Progressive disclosure for technical details */}\n {hasPayload && (\n <button\n onClick={() => toggleExpanded(event.id)}\n className=\"flex items-center gap-1 mt-2 text-xs text-primary-600 hover:text-primary-700 hover:underline focus:outline-none focus:ring-2 focus:ring-primary-500 px-1 py-0.5 rounded\"\n >\n {isExpanded ? (\n <>\n <ChevronUp className=\"h-3 w-3\" />\n {hideDetailsLabel}\n </>\n ) : (\n <>\n <ChevronDown className=\"h-3 w-3\" />\n {showDetailsLabel}\n </>\n )}\n </button>\n )}\n \n {isExpanded && formatPayload(event.payload)}\n </div>\n \n {/* BIRHAUS: Spanish-first time formatting */}\n <div className=\"flex items-center gap-1 text-xs text-gray-500 whitespace-nowrap\">\n <Clock className=\"h-3 w-3\" />\n <span title={new Date(event.created_at).toLocaleString('es-ES')}>\n {formatDistanceToNow(new Date(event.created_at), { \n addSuffix: true,\n locale: es \n })}\n </span>\n </div>\n </div>\n </div>\n </div>\n )\n })\n )}\n </div>\n </div>\n )\n}\n\n// BIRHAUS: Hook for audit event management\nexport function useAuditTimeline() {\n const [events, setEvents] = useState<AuditEvent[]>([])\n \n const addEvent = (event: Omit<AuditEvent, 'id' | 'created_at'>) => {\n const newEvent: AuditEvent = {\n ...event,\n id: `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n created_at: new Date().toISOString()\n }\n \n setEvents(prev => [newEvent, ...prev])\n return newEvent\n }\n \n const clearEvents = () => {\n setEvents([])\n }\n \n return {\n events,\n addEvent,\n clearEvents\n }\n}\n\n// Components and hooks are exported via individual export declarations above"]}