Files
hefesto/frontend/src/pages/Relatorios.tsx

237 lines
10 KiB
TypeScript

import { useState, useEffect } from 'react'
import { FileText, Loader2, Download, Calendar, TrendingUp, Building2, ClipboardList, Leaf } from 'lucide-react'
import api from '../services/api'
type TabKey = 'orcamento' | 'demandas' | 'fornecedores' | 'os' | 'esg_impacto' | 'esg_fornecedores' | 'esg_preventiva' | 'esg_governanca'
const tabs: { key: TabKey; label: string; icon: React.ReactNode; esg?: boolean }[] = [
{ key: 'orcamento', label: 'Orçamento', icon: <TrendingUp className="w-4 h-4" /> },
{ key: 'demandas', label: 'Demandas', icon: <FileText className="w-4 h-4" /> },
{ key: 'fornecedores', label: 'Fornecedores', icon: <Building2 className="w-4 h-4" /> },
{ key: 'os', label: 'OS', icon: <ClipboardList className="w-4 h-4" /> },
{ key: 'esg_impacto', label: 'Impacto Ambiental', icon: <Leaf className="w-4 h-4" />, esg: true },
{ key: 'esg_fornecedores', label: 'ESG Fornecedores', icon: <Leaf className="w-4 h-4" />, esg: true },
{ key: 'esg_preventiva', label: 'Evolução Preventiva', icon: <Leaf className="w-4 h-4" />, esg: true },
{ key: 'esg_governanca', label: 'Exceções Governança', icon: <Leaf className="w-4 h-4" />, esg: true },
]
export default function Relatorios() {
const [activeTab, setActiveTab] = useState<TabKey>('orcamento')
const [loading, setLoading] = useState(true)
const [data, setData] = useState<any>(null)
const [dateFrom, setDateFrom] = useState('')
const [dateTo, setDateTo] = useState('')
useEffect(() => { fetchData() }, [activeTab, dateFrom, dateTo])
const endpoints: Record<TabKey, string> = {
orcamento: '/relatorios/orcamento-mensal',
demandas: '/relatorios/demandas-periodo',
fornecedores: '/relatorios/fornecedores-ranking',
os: '/relatorios/os-performance',
esg_impacto: '/relatorios/esg-impacto-ambiental',
esg_fornecedores: '/relatorios/esg-fornecedores',
esg_preventiva: '/relatorios/esg-evolucao-preventiva',
esg_governanca: '/relatorios/esg-excecoes-governanca',
}
const fetchData = async () => {
setLoading(true)
try {
const params: any = {}
if (dateFrom) params.data_inicio = dateFrom
if (dateTo) params.data_fim = dateTo
const { data } = await api.get(endpoints[activeTab], { params })
setData(data)
} catch (err) {
console.error('Error fetching report:', err)
setData(null)
} finally {
setLoading(false)
}
}
const renderEsgImpacto = () => {
if (!data) return null
return (
<div className="space-y-4">
{data.resumo && (
<div className="grid grid-cols-3 gap-4 mb-4">
{data.resumo.map((r: any) => (
<div key={r.impacto} className={`p-4 rounded-xl ${r.impacto === 'Alto' ? 'bg-red-50' : r.impacto === 'Médio' ? 'bg-amber-50' : 'bg-green-50'}`}>
<p className="text-sm text-gray">Impacto {r.impacto}</p>
<p className="text-2xl font-bold">{r.total}</p>
</div>
))}
</div>
)}
{renderGenericTable(data.detalhes || [])}
</div>
)
}
const renderEsgFornecedores = () => {
if (!data) return null
return (
<div className="space-y-4">
{data.resumo && (
<div className="grid grid-cols-3 gap-4 mb-4">
{data.resumo.map((r: any) => (
<div key={r.classificacao_esg} className={`p-4 rounded-xl ${r.classificacao_esg === 'Avançado' ? 'bg-green-50' : r.classificacao_esg === 'Intermediário' ? 'bg-amber-50' : 'bg-red-50'}`}>
<p className="text-sm text-gray">{r.classificacao_esg}</p>
<p className="text-2xl font-bold">{r.total}</p>
</div>
))}
</div>
)}
{data.fornecedores && (
<div className="overflow-x-auto">
<table className="w-full">
<thead><tr className="table-header">
<th className="table-cell">Fornecedor</th>
<th className="table-cell">ESG</th>
<th className="table-cell">Rating</th>
<th className="table-cell">Pol. Ambiental</th>
<th className="table-cell">SST</th>
<th className="table-cell">EPI</th>
<th className="table-cell">Treinada</th>
<th className="table-cell">Total OS</th>
</tr></thead>
<tbody>
{data.fornecedores.map((f: any) => (
<tr key={f.id} className="table-row">
<td className="table-cell font-medium">{f.razao_social}</td>
<td className="table-cell">
<span className={`badge ${f.classificacao_esg === 'Avançado' ? 'badge-success' : f.classificacao_esg === 'Intermediário' ? 'badge-warning' : 'badge-error'}`}>
{f.classificacao_esg || '-'}
</span>
</td>
<td className="table-cell">{Number(f.rating).toFixed(1)}</td>
<td className="table-cell">{f.possui_politica_ambiental ? '✅' : '❌'}</td>
<td className="table-cell">{f.possui_politica_sst ? '✅' : '❌'}</td>
<td className="table-cell">{f.declara_uso_epi ? '✅' : '❌'}</td>
<td className="table-cell">{f.equipe_treinada ? '✅' : '❌'}</td>
<td className="table-cell">{f.total_os}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
)
}
const renderGenericTable = (items: any[]) => {
if (!items || items.length === 0) return <p className="text-gray text-center py-8">Nenhum dado.</p>
const keys = Object.keys(items[0]).filter(k => k !== 'id')
return (
<div className="overflow-x-auto">
<table className="w-full">
<thead><tr className="table-header">
{keys.map(k => <th key={k} className="table-cell text-left capitalize">{k.replace(/_/g, ' ')}</th>)}
</tr></thead>
<tbody>
{items.map((item: any, i: number) => (
<tr key={i} className="table-row">
{keys.map(k => (
<td key={k} className="table-cell text-sm">
{typeof item[k] === 'number' ? item[k].toLocaleString('pt-BR', { maximumFractionDigits: 2 }) : String(item[k] ?? '-')}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
const renderContent = () => {
if (loading) return <div className="flex justify-center py-12"><Loader2 className="w-8 h-8 animate-spin text-primary" /></div>
if (!data) return <div className="text-center py-12"><FileText className="w-16 h-16 text-gray-300 mx-auto mb-4" /><p className="text-gray">Nenhum dado disponível.</p></div>
// ESG specific renderers
if (activeTab === 'esg_impacto') return renderEsgImpacto()
if (activeTab === 'esg_fornecedores') return renderEsgFornecedores()
if (activeTab === 'esg_governanca') {
const itens = data.itens || []
return (
<div>
<p className="text-sm text-gray mb-4">Total de exceções: <strong>{data.total}</strong></p>
{renderGenericTable(itens.map((w: any) => ({
demanda: w.demanda_numero ? `#${w.demanda_numero} - ${w.demanda_titulo}` : w.demanda_titulo,
valor: w.valor_total,
status: w.status,
})))}
</div>
)
}
// Generic
const items = Array.isArray(data) ? data : data?.items || data?.dados || data?.detalhes || []
if (Array.isArray(items) && items.length > 0) return renderGenericTable(items)
if (typeof data === 'object' && !Array.isArray(data)) {
const entries = Object.entries(data).filter(([k]) => !['items', 'dados', 'detalhes', 'itens', 'resumo', 'fornecedores'].includes(k))
if (entries.length > 0) {
return (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{entries.map(([k, v]) => (
<div key={k} className="p-4 bg-gray-50 rounded-xl">
<p className="text-sm text-gray capitalize">{k.replace(/_/g, ' ')}</p>
<p className="text-xl font-bold text-text">
{typeof v === 'number' ? v.toLocaleString('pt-BR', { maximumFractionDigits: 2 }) : String(v)}
</p>
</div>
))}
</div>
)
}
}
return <p className="text-center text-gray py-8">Formato de dados não reconhecido.</p>
}
return (
<div className="space-y-6 animate-fade-in">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-text flex items-center gap-2">
<FileText className="w-8 h-8 text-primary" /> Relatórios
</h1>
<p className="text-gray mt-1">Relatórios detalhados por área.</p>
</div>
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4 text-gray" />
<input type="date" value={dateFrom} onChange={e => setDateFrom(e.target.value)} className="input-field text-sm" />
<span className="text-gray"></span>
<input type="date" value={dateTo} onChange={e => setDateTo(e.target.value)} className="input-field text-sm" />
</div>
</div>
{/* Tabs */}
<div className="flex gap-1 bg-gray-100 p-1 rounded-xl overflow-x-auto">
{tabs.map(tab => (
<button
key={tab.key}
onClick={() => setActiveTab(tab.key)}
className={`flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium transition-all whitespace-nowrap ${
activeTab === tab.key
? `bg-white shadow-sm ${tab.esg ? '' : 'text-primary'}`
: 'text-gray hover:text-text'
}`}
style={activeTab === tab.key && tab.esg ? { color: '#1A7A4C' } : {}}
>
{tab.icon} {tab.label}
</button>
))}
</div>
{/* Content */}
<div className="card">
{renderContent()}
</div>
</div>
)
}