Major update: ESG, KPIs, metas, alertas, auditoria, documentos, importação, relatórios, subcategorias, dashboard orçamentos
This commit is contained in:
@@ -1,278 +1,235 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
BarChart3,
|
||||
Download,
|
||||
Calendar,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
PieChart,
|
||||
FileText,
|
||||
Filter
|
||||
} from 'lucide-react'
|
||||
import {
|
||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||
LineChart, Line, PieChart as RechartsPie, Pie, Cell, Legend,
|
||||
AreaChart, Area
|
||||
} from 'recharts'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { FileText, Loader2, Download, Calendar, TrendingUp, Building2, ClipboardList, Leaf } from 'lucide-react'
|
||||
import api from '../services/api'
|
||||
|
||||
const monthlyData = [
|
||||
{ name: 'Jan', orcamento: 120, gasto: 95, economia: 25 },
|
||||
{ name: 'Fev', orcamento: 115, gasto: 110, economia: 5 },
|
||||
{ name: 'Mar', orcamento: 130, gasto: 105, economia: 25 },
|
||||
{ name: 'Abr', orcamento: 125, gasto: 120, economia: 5 },
|
||||
{ name: 'Mai', orcamento: 140, gasto: 115, economia: 25 },
|
||||
{ name: 'Jun', orcamento: 135, gasto: 125, economia: 10 },
|
||||
]
|
||||
type TabKey = 'orcamento' | 'demandas' | 'fornecedores' | 'os' | 'esg_impacto' | 'esg_fornecedores' | 'esg_preventiva' | 'esg_governanca'
|
||||
|
||||
const categoryData = [
|
||||
{ name: 'Manutenção', value: 35, color: '#E65100' },
|
||||
{ name: 'Limpeza', value: 25, color: '#1A237E' },
|
||||
{ name: 'Segurança', value: 20, color: '#FF8F00' },
|
||||
{ name: 'Utilities', value: 12, color: '#2E7D32' },
|
||||
{ name: 'Outros', value: 8, color: '#757575' },
|
||||
]
|
||||
|
||||
const trendData = [
|
||||
{ name: 'Sem 1', demandas: 15, os: 12 },
|
||||
{ name: 'Sem 2', demandas: 22, os: 18 },
|
||||
{ name: 'Sem 3', demandas: 18, os: 16 },
|
||||
{ name: 'Sem 4', demandas: 25, os: 22 },
|
||||
]
|
||||
|
||||
const fornecedorData = [
|
||||
{ name: 'Tech Solutions', atendimentos: 45, satisfacao: 4.5 },
|
||||
{ name: 'EletroFix', atendimentos: 38, satisfacao: 4.8 },
|
||||
{ name: 'HidroServ', atendimentos: 32, satisfacao: 4.2 },
|
||||
{ name: 'CleanPro', atendimentos: 28, satisfacao: 4.6 },
|
||||
{ name: 'ElevaTech', atendimentos: 15, satisfacao: 3.9 },
|
||||
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 [period, setPeriod] = useState('mensal')
|
||||
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">
|
||||
{/* Header */}
|
||||
<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">Relatórios</h1>
|
||||
<p className="text-gray mt-1">Análises e métricas de facilities</p>
|
||||
<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 gap-3">
|
||||
<select
|
||||
value={period}
|
||||
onChange={(e) => setPeriod(e.target.value)}
|
||||
className="input-field w-40"
|
||||
<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' } : {}}
|
||||
>
|
||||
<option value="semanal">Semanal</option>
|
||||
<option value="mensal">Mensal</option>
|
||||
<option value="trimestral">Trimestral</option>
|
||||
<option value="anual">Anual</option>
|
||||
</select>
|
||||
<button className="btn-primary flex items-center gap-2">
|
||||
<Download className="w-5 h-5" />
|
||||
Exportar
|
||||
{tab.icon} {tab.label}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Summary Cards */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="card">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
||||
<TrendingUp className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<span className="text-sm text-gray">Economia Total</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-text">95K</p>
|
||||
<p className="text-xs text-green-600 mt-1">+12% vs período anterior</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-lg bg-secondary/10 flex items-center justify-center">
|
||||
<FileText className="w-5 h-5 text-secondary" />
|
||||
</div>
|
||||
<span className="text-sm text-gray">Demandas</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-text">156</p>
|
||||
<p className="text-xs text-green-600 mt-1">+8% vs período anterior</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-lg bg-accent/10 flex items-center justify-center">
|
||||
<BarChart3 className="w-5 h-5 text-accent" />
|
||||
</div>
|
||||
<span className="text-sm text-gray">OS Concluídas</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-text">142</p>
|
||||
<p className="text-xs text-green-600 mt-1">91% taxa de conclusão</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
|
||||
<TrendingDown className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<span className="text-sm text-gray">Tempo Médio</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-text">3.2 dias</p>
|
||||
<p className="text-xs text-green-600 mt-1">-15% vs período anterior</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Charts Row 1 */}
|
||||
<div className="grid lg:grid-cols-2 gap-6">
|
||||
{/* Bar Chart - Orçamento vs Gasto */}
|
||||
<div className="card">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-text">Orçamento vs Gasto</h2>
|
||||
<p className="text-sm text-gray">Comparativo mensal (em milhares)</p>
|
||||
</div>
|
||||
<button className="p-2 rounded-lg hover:bg-gray-100 text-gray">
|
||||
<Filter className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-72">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={monthlyData} barGap={8}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#E0E0E0" vertical={false} />
|
||||
<XAxis dataKey="name" axisLine={false} tickLine={false} tick={{ fill: '#757575', fontSize: 12 }} />
|
||||
<YAxis axisLine={false} tickLine={false} tick={{ fill: '#757575', fontSize: 12 }} />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: '#fff',
|
||||
border: '1px solid #E0E0E0',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)'
|
||||
}}
|
||||
/>
|
||||
<Bar dataKey="orcamento" fill="#1A237E" radius={[4, 4, 0, 0]} name="Orçamento" />
|
||||
<Bar dataKey="gasto" fill="#E65100" radius={[4, 4, 0, 0]} name="Gasto" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pie Chart - Por Categoria */}
|
||||
<div className="card">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-text">Gastos por Categoria</h2>
|
||||
<p className="text-sm text-gray">Distribuição percentual</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-72">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<RechartsPie>
|
||||
<Pie
|
||||
data={categoryData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius={60}
|
||||
outerRadius={100}
|
||||
paddingAngle={4}
|
||||
dataKey="value"
|
||||
>
|
||||
{categoryData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
<Legend
|
||||
verticalAlign="bottom"
|
||||
iconType="circle"
|
||||
iconSize={8}
|
||||
formatter={(value) => <span className="text-sm text-gray">{value}</span>}
|
||||
/>
|
||||
<Tooltip />
|
||||
</RechartsPie>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Charts Row 2 */}
|
||||
<div className="grid lg:grid-cols-2 gap-6">
|
||||
{/* Area Chart - Tendência */}
|
||||
<div className="card">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-text">Tendência Semanal</h2>
|
||||
<p className="text-sm text-gray">Demandas vs Ordens de Serviço</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={trendData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#E0E0E0" vertical={false} />
|
||||
<XAxis dataKey="name" axisLine={false} tickLine={false} tick={{ fill: '#757575', fontSize: 12 }} />
|
||||
<YAxis axisLine={false} tickLine={false} tick={{ fill: '#757575', fontSize: 12 }} />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: '#fff',
|
||||
border: '1px solid #E0E0E0',
|
||||
borderRadius: '12px'
|
||||
}}
|
||||
/>
|
||||
<Area type="monotone" dataKey="demandas" stroke="#E65100" fill="#E65100" fillOpacity={0.2} name="Demandas" />
|
||||
<Area type="monotone" dataKey="os" stroke="#1A237E" fill="#1A237E" fillOpacity={0.2} name="Ordens de Serviço" />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bar Chart - Fornecedores */}
|
||||
<div className="card">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-text">Top Fornecedores</h2>
|
||||
<p className="text-sm text-gray">Por número de atendimentos</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={fornecedorData} layout="vertical" barSize={20}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#E0E0E0" horizontal={false} />
|
||||
<XAxis type="number" axisLine={false} tickLine={false} tick={{ fill: '#757575', fontSize: 12 }} />
|
||||
<YAxis type="category" dataKey="name" axisLine={false} tickLine={false} tick={{ fill: '#757575', fontSize: 12 }} width={100} />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: '#fff',
|
||||
border: '1px solid #E0E0E0',
|
||||
borderRadius: '12px'
|
||||
}}
|
||||
/>
|
||||
<Bar dataKey="atendimentos" fill="#FF8F00" radius={[0, 4, 4, 0]} name="Atendimentos" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Reports */}
|
||||
{/* Content */}
|
||||
<div className="card">
|
||||
<h2 className="text-lg font-semibold text-text mb-4">Relatórios Disponíveis</h2>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ name: 'Relatório Mensal', desc: 'Resumo completo do mês', icon: <Calendar className="w-5 h-5" /> },
|
||||
{ name: 'Análise de Custos', desc: 'Detalhamento por categoria', icon: <PieChart className="w-5 h-5" /> },
|
||||
{ name: 'Performance Fornecedores', desc: 'Avaliação e métricas', icon: <TrendingUp className="w-5 h-5" /> },
|
||||
{ name: 'Histórico de Demandas', desc: 'Todas as solicitações', icon: <FileText className="w-5 h-5" /> },
|
||||
].map((report, i) => (
|
||||
<button
|
||||
key={i}
|
||||
className="flex items-center gap-4 p-4 bg-card rounded-xl hover:bg-gray-100 transition-all text-left"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
|
||||
{report.icon}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-text">{report.name}</p>
|
||||
<p className="text-xs text-gray">{report.desc}</p>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user