import React, { useState, useEffect, useCallback } from 'react'; const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3002'; interface Entrada { id: number; inicio: string; fin: string | null; descripcion: string; estado: string; modo: string; es_ia: boolean; nodo_nombre: string; nodo_ip: string; } interface NodoGroup { nodo: string; ip: string; entradas: Entrada[]; } interface Bitacora { id: number; fecha: string; resumen: string; nodos: NodoGroup[]; } interface Nodo { id: number; nombre: string; ip: string; } function App() { const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]); const [bitacora, setBitacora] = useState(null); const [nodos, setNodos] = useState([]); const [loading, setLoading] = useState(true); // Form state const [newInicio, setNewInicio] = useState(''); const [newFin, setNewFin] = useState(''); const [newDesc, setNewDesc] = useState(''); const [newEstado, setNewEstado] = useState('⏳'); const [newModo, setNewModo] = useState('P'); const [newNodoId, setNewNodoId] = useState(''); const [newEsIa, setNewEsIa] = useState(false); const fetchBitacora = useCallback(async () => { setLoading(true); try { const res = await fetch(`${API_URL}/api/bitacoras/${fecha}/completa`); const data = await res.json(); setBitacora(data); } catch (err) { console.error('Error:', err); } setLoading(false); }, [fecha]); const fetchNodos = async () => { try { const res = await fetch(`${API_URL}/api/nodos`); setNodos(await res.json()); } catch (err) { console.error('Error:', err); } }; useEffect(() => { fetchBitacora(); }, [fetchBitacora]); useEffect(() => { fetchNodos(); }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!bitacora || !newDesc || !newInicio || !newNodoId) return; try { await fetch(`${API_URL}/api/entradas`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ inicio: newInicio, fin: newFin || null, descripcion: newDesc, estado: newEstado, modo: newModo, es_ia: newEsIa, bitacora_id: bitacora.id, nodo_id: parseInt(newNodoId) }) }); setNewDesc(''); setNewInicio(''); setNewFin(''); fetchBitacora(); } catch (err) { console.error('Error:', err); } }; const handleDelete = async (id: number) => { await fetch(`${API_URL}/api/entradas/${id}`, { method: 'DELETE' }); fetchBitacora(); }; const totalEntradas = bitacora?.nodos?.reduce((sum, n) => sum + n.entradas.length, 0) || 0; const totalNodos = bitacora?.nodos?.length || 0; return (
{/* Date Selector */}
setFecha(e.target.value)} />
{/* Stats */}
{totalEntradas}
Entradas
{totalNodos}
Nodos Activos
{bitacora?.nodos?.reduce((s, n) => s + n.entradas.filter(e => e.estado === '✅').length, 0) || 0}
Completadas
{bitacora?.nodos?.reduce((s, n) => s + n.entradas.filter(e => e.estado === '⏳').length, 0) || 0}
En Proceso
{/* New Entry Form */}
Nueva Entrada
setNewInicio(e.target.value)} placeholder="I" required /> setNewFin(e.target.value)} placeholder="F" /> setNewDesc(e.target.value)} placeholder="Descripción..." required />
{/* Loading */} {loading &&
} {/* Entries by Node */} {bitacora?.nodos?.map((nodoGroup, idx) => (
{nodoGroup.nodo} {nodoGroup.ip && ({nodoGroup.ip})} {nodoGroup.entradas.length}
{nodoGroup.entradas.map(e => ( ))}
I F Descripción E
{e.inicio?.substring(0, 5)} {e.fin ? e.fin.substring(0, 5) : '—'} {e.es_ia && IA} {e.descripcion} [{e.modo}] {e.estado}
))} {!loading && totalEntradas === 0 && (
Sin entradas para esta fecha. ¡Creá la primera!
)}
); } export default App;