HEFESTO v1.0 - Sistema de Controle Orçamentário para Facilities

- Backend NestJS com 12 módulos
- Frontend React com dashboard e gestão
- Manuais técnico e de negócios (MD + PDF)
- Workflow de aprovação com alçadas
- RBAC com 6 perfis de acesso
This commit is contained in:
2026-02-09 14:53:01 -03:00
commit d8ca580acb
107 changed files with 22657 additions and 0 deletions

View File

@@ -0,0 +1,279 @@
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'
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 },
]
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 },
]
export default function Relatorios() {
const [period, setPeriod] = useState('mensal')
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>
</div>
<div className="flex gap-3">
<select
value={period}
onChange={(e) => setPeriod(e.target.value)}
className="input-field w-40"
>
<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
</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 */}
<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>
</div>
</div>
)
}