// Dashboard — sidebar + topbar + content (grid/list) + detail panel. const { useState: useStateD, useMemo, useEffect, useRef } = React; /* ------------------------------------------------------------------ */ /* Thumb renderer */ /* ------------------------------------------------------------------ */ function Thumb({ item, large = false }) { const [imgError, setImgError] = useStateD(false); // Si hay thumbnail real en el servidor, usarlo if (item.thumbnail_url && !imgError) { return ( {item.name} setImgError(true)} /> ); } // Fallback generativo basado en tipo const colors = { image: { a: "#1B1A1A", b: "#00B147" }, pdf: { a: "#DC2626", b: "#FEF2F2" }, docx: { a: "#1D4ED8", b: "#EFF6FF" }, xlsx: { a: "#065F46", b: "#ECFDF5" }, pptx: { a: "#EA580C", b: "#FFF7ED" }, zip: { a: "#7C3AED", b: "#F5F3FF" }, video: { a: "#0E7490", b: "#ECFEFF" }, }; const c = colors[item.type] || { a: "#6B7280", b: "#F9FAFB" }; return (
{item.type}
); } const typeIcon = (t) => ({ pdf: "pdf", xlsx: "xlsx", docx: "docx", zip: "zip", image: "image", }[t] || "document"); const fmtDate = (s) => { const d = new Date(s); if (isNaN(d)) return s; return d.toLocaleDateString("es-MX", { day: "2-digit", month: "short", year: "numeric" }); }; const fmtSize = (bytes) => { if (!bytes) return "—"; if (bytes < 1024) return bytes + " B"; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB"; if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + " MB"; return (bytes / 1073741824).toFixed(2) + " GB"; }; /* ------------------------------------------------------------------ */ /* Sidebar */ /* ------------------------------------------------------------------ */ function Sidebar({ sections, folders, currentUser, sectionId, setSection, folderId, setFolder, favorites, onLogout, onOpenAdmin }) { return ( ); } /* ------------------------------------------------------------------ */ /* Topbar */ /* ------------------------------------------------------------------ */ function Topbar({ query, setQuery, onUpload }) { return (
setQuery(e.target.value)} placeholder="Buscar en todo el acervo — archivos, etiquetas, autores…" /> ⌘ K
); } /* ------------------------------------------------------------------ */ /* Card */ /* ------------------------------------------------------------------ */ function Card({ item, onOpen, onDownload }) { return (
onOpen(item)}>
{item.type}

{item.name}

{fmtSize(item.size_bytes)} {fmtDate(item.updated_at)} v{item.version}
); } /* ------------------------------------------------------------------ */ /* Row (list view) */ /* ------------------------------------------------------------------ */ function Row({ item, folderName, onOpen, onDownload }) { return (
onOpen(item)}>
{item.name}
{item.description}
{folderName}
{item.type}
{fmtDate(item.updated_at)}
{fmtSize(item.size_bytes)}
); } /* ------------------------------------------------------------------ */ /* Upload modal simplificado */ /* ------------------------------------------------------------------ */ function UploadModal({ sectionId, folderId, onClose, onSuccess }) { const [file, setFile] = useStateD(null); const [name, setName] = useStateD(""); const [desc, setDesc] = useStateD(""); const [tags, setTags] = useStateD(""); const [uploading, setUploading] = useStateD(false); const [error, setError] = useStateD(null); const handleSubmit = async (e) => { e.preventDefault(); if (!file) { setError("Selecciona un archivo"); return; } setUploading(true); setError(null); try { const fd = new FormData(); fd.append("file", file); fd.append("section_id", sectionId); if (folderId) fd.append("folder_id", folderId); fd.append("name", name || file.name); fd.append("description", desc); fd.append("tags", tags); await API.Items.upload(fd); onSuccess(); onClose(); } catch(err) { setError(err.message || "Error al subir el archivo"); } finally { setUploading(false); } }; return (
e.stopPropagation()} onSubmit={handleSubmit} >
Subir archivo
{error &&
{error}
}
{ setFile(e.target.files[0]); if(!name) setName(e.target.files[0]?.name||""); }} required style={{fontSize:13}}/>
setName(e.target.value)} placeholder="Nombre del archivo"/>
setDesc(e.target.value)} placeholder="Descripción breve"/>
setTags(e.target.value)} placeholder="contrato, 2024, rh"/>
); } /* ------------------------------------------------------------------ */ /* Detail panel */ /* ------------------------------------------------------------------ */ function DetailPanel({ item, folderName, onClose, onDownload, favorites, onToggleFav }) { if (!item) return null; const isFav = favorites.includes(item.id); return ( <>
); } /* ------------------------------------------------------------------ */ /* Main Dashboard */ /* ------------------------------------------------------------------ */ function Dashboard({ sections, allFolders, currentUser, onLogout, onOpenAdmin }) { const [sectionId, setSection] = useStateD(sections[0]?.id || null); const [folderId, setFolder] = useStateD(null); const [query, setQuery] = useStateD(""); const [view, setView] = useStateD("grid"); const [typeFilter, setType] = useStateD("all"); const [sort, setSort] = useStateD("recent"); const [openItem, setOpenItem] = useStateD(null); const [toast, setToast] = useStateD(null); const [showUpload, setShowUpload] = useStateD(false); // Items cargados del servidor const [items, setItems] = useStateD([]); const [loadingItems, setLoading] = useStateD(false); const [totalItems, setTotal] = useStateD(0); // Favoritos const [favorites, setFavorites] = useStateD([]); const debounceRef = useRef(null); // Inicializar sectionId cuando sections lleguen useEffect(() => { if (!sectionId && sections.length > 0) { setSection(sections[0].id); } }, [sections]); // Cargar items al cambiar sección/carpeta (sin búsqueda activa) useEffect(() => { if (!sectionId) return; if (query.trim().length > 1) return; // búsqueda activa maneja sus propios items setLoading(true); const params = { section_id: sectionId, per_page: 100, sort }; if (folderId) params.folder_id = folderId; API.Items.list(params) .then(function(res) { setItems(res.data || []); setTotal(res.total || 0); // Extraer favoritos const favIds = (res.data || []).filter(i => i.is_favorite).map(i => i.id); setFavorites(function(prev) { const merged = new Set([...prev, ...favIds]); return Array.from(merged); }); }) .catch(function() { setItems([]); }) .finally(function() { setLoading(false); }); }, [sectionId, folderId, sort]); // Búsqueda con debounce useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current); if (!query.trim() || query.trim().length < 2) { // Sin query: recargar items normales if (!query.trim() && sectionId) { setLoading(true); const params = { section_id: sectionId, per_page: 100, sort }; if (folderId) params.folder_id = folderId; API.Items.list(params) .then(function(res) { setItems(res.data || []); setTotal(res.total || 0); }) .catch(function() {}) .finally(function() { setLoading(false); }); } return; } debounceRef.current = setTimeout(function() { setLoading(true); const params = { q: query.trim(), per_page: 50 }; if (sectionId) params.section_id = sectionId; API.Search.query(params) .then(function(res) { setItems(res.data || []); setTotal(res.total || 0); }) .catch(function() {}) .finally(function() { setLoading(false); }); }, 350); }, [query]); // ESC para cerrar detail panel useEffect(() => { const h = (e) => { if (e.key === "Escape") setOpenItem(null); }; window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); }, []); // Filtros cliente (sobre items ya cargados) const filtered = useMemo(() => { let out = items; if (typeFilter !== "all") out = out.filter(i => i.type === typeFilter); if (sort === "name") out = [...out].sort((a,b) => a.name.localeCompare(b.name)); if (sort === "size") out = [...out].sort((a,b) => b.size_bytes - a.size_bytes); return out; }, [items, typeFilter, sort]); // Tipos disponibles const types = useMemo(() => { const set = new Set(items.map(i => i.type)); return Array.from(set); }, [items]); const section = sections.find(s => s.id === sectionId); const folders = allFolders[sectionId] || []; const folder = folders.find(f => f.id === folderId); const handleDownload = function(item) { window.open(API.Items.downloadUrl(item.id), "_blank"); }; const handleToggleFav = async function(item) { const isFav = favorites.includes(item.id); try { if (isFav) { await API.Items.unfavorite(item.id); setFavorites(function(prev) { return prev.filter(id => id !== item.id); }); } else { await API.Items.favorite(item.id); setFavorites(function(prev) { return [...prev, item.id]; }); } } catch(e) { setToast("Error al actualizar favorito"); setTimeout(() => setToast(null), 2000); } }; const handleUploadSuccess = function() { // Recargar items if (!sectionId) return; const params = { section_id: sectionId, per_page: 100 }; if (folderId) params.folder_id = folderId; API.Items.list(params).then(function(res) { setItems(res.data || []); }).catch(function() {}); setToast("Archivo subido correctamente"); setTimeout(() => setToast(null), 2000); }; if (!section && sections.length > 0) { return
Cargando sección…
; } return (
setShowUpload(true)}/>
{/* Crumbs */}
Biblioteca {section?.label} {folder && <> {folder.name} } {!folder && Todo}
{/* Page head */}
Sección

{folder ? folder.name : section?.label}

{section?.description}

Total
{totalItems.toLocaleString("es-MX")}
{/* Folder strip */} {!folderId && folders.length > 0 && ( <>
Carpetas {folders.length} carpetas
{folders.map(f => (
setFolder(f.id)}>
{f.name}
{(f.items_count || 0).toLocaleString("es-MX")} elementos
))}
+ Nueva carpeta
Expandir esta sección
)} {/* Section label */}
{folder ? "Contenido" : "Archivos"} {filtered.length} resultado{filtered.length===1?"":"s"}
{/* Toolbar */}
{types.map(tp => ( ))}
{/* Loading */} {loadingItems && (
Cargando archivos…
)} {/* Empty */} {!loadingItems && filtered.length === 0 && (
Sin resultados
Ajuste su búsqueda o limpie los filtros.
)} {/* Grid */} {!loadingItems && filtered.length > 0 && view === "grid" && (
{filtered.map(i => ( ))}
)} {/* List */} {!loadingItems && filtered.length > 0 && view === "list" && (
Archivo
Carpeta
Tipo
Actualizado
Tamaño
{filtered.map(i => ( f.id===i.folder_id)?.name || "—"} onOpen={setOpenItem} onDownload={handleDownload}/> ))}
)}
{/* Detail */} f.id === openItem.folder_id)?.name : null} onClose={() => setOpenItem(null)} onDownload={handleDownload} favorites={favorites} onToggleFav={handleToggleFav} /> {/* Upload modal */} {showUpload && ( setShowUpload(false)} onSuccess={handleUploadSuccess} /> )} {/* Toast */} {toast && (
{toast}
)}
); } window.Dashboard = Dashboard;