import { useEffect, useMemo, useState } from 'react'; import { AlertTriangle, CheckCircle2, Database, PackageSearch, RefreshCw } from 'lucide-react'; import PaginationControls from '../components/PaginationControls'; import { fetchDataHealth } from '../dataService'; import type { DataHealthIssue, DataHealthSummary } from '../types'; const emptySummary: DataHealthSummary = { totals: { products: 0, productsWithComposition: 0, productsWithStockLink: 0, materials: 0, materialsWithStock: 0, compositions: 0, components: 0 }, issues: [], }; const formatNumber = (value: number) => new Intl.NumberFormat('pt-BR').format(value); const percent = (part: number, total: number) => total ? Math.round((part / total) * 100) : 0; const severityStyle: Record = { critical: 'border-red-400/30 bg-red-400/10 text-red-300', attention: 'border-amber-400/30 bg-amber-400/10 text-amber-300', info: 'border-sky-400/30 bg-sky-400/10 text-sky-300', }; const DataHealth = () => { const [summary, setSummary] = useState(emptySummary); const [isLoading, setIsLoading] = useState(true); const [filter, setFilter] = useState<'all' | DataHealthIssue['type']>('all'); const [search, setSearch] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(20); const load = async () => { setIsLoading(true); const next = await fetchDataHealth(); if (next) setSummary(next); setIsLoading(false); }; useEffect(() => { const timer = window.setTimeout(() => { void load(); }, 0); return () => window.clearTimeout(timer); }, []); const filteredIssues = useMemo(() => { const query = search.trim().toLowerCase(); return summary.issues.filter(issue => ( (filter === 'all' || issue.type === filter) && (!query || `${issue.title} ${issue.detail} ${issue.productSku} ${issue.component}`.toLowerCase().includes(query)) )); }, [filter, search, summary.issues]); const totalPages = Math.ceil(filteredIssues.length / itemsPerPage); const safePage = Math.min(currentPage, totalPages || 1); const startIndex = (safePage - 1) * itemsPerPage; const visibleIssues = filteredIssues.slice(startIndex, startIndex + itemsPerPage); const count = (type: DataHealthIssue['type']) => summary.issues.filter(issue => issue.type === type).length; const metrics = [ { label: 'Produtos com composição', value: percent(summary.totals.productsWithComposition, summary.totals.products), detail: `${formatNumber(summary.totals.productsWithComposition)} de ${formatNumber(summary.totals.products)}` }, { label: 'Produtos com estoque', value: percent(summary.totals.productsWithStockLink, summary.totals.products), detail: `${formatNumber(summary.totals.productsWithStockLink)} de ${formatNumber(summary.totals.products)}` }, { label: 'Materiais com estoque', value: percent(summary.totals.materialsWithStock, summary.totals.materials), detail: `${formatNumber(summary.totals.materialsWithStock)} de ${formatNumber(summary.totals.materials)}` }, ]; return (

Saúde dos Dados

Confira se composição, vínculo de estoque e consumo estão prontos para orientar compra e corte.

{metrics.map(metric => (

{metric.label}

{metric.value}%

{metric.detail}

))}
{[ ['missing_product_sku', 'Sem SKU'], ['component_without_stock_link', 'Sem vínculo'], ['suspicious_quantity', 'Qtd. suspeita'], ['raw_or_service_structure', 'Matéria-prima/serviço'], ['product_without_composition', 'Sem composição'], ].map(([type, label]) => ( ))}
{ setSearch(event.target.value); setCurrentPage(1); }} placeholder="Buscar produto, SKU ou componente..." className="h-10 flex-1 rounded-xl border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none focus:border-brand-primary" />
{isLoading ?
Verificando dados…
: !visibleIssues.length ?
Nenhuma pendência neste filtro.
: ( <>
{visibleIssues.map((issue, index) =>

{issue.title}

{issue.severity === 'critical' ? 'Crítico' : issue.severity === 'attention' ? 'Revisar' : 'Informativo'}

{issue.detail}

{(issue.productSku || issue.component) &&

{[issue.productSku && `SKU ${issue.productSku}`, issue.component && `Comp. ${issue.component}`].filter(Boolean).join(' · ')}

}
)}
{ setItemsPerPage(size); setCurrentPage(1); }} className="border-t border-dark-border px-4 py-3" /> )}
); }; export default DataHealth;