Calendário de férias · 2026
Linha = período de gozo · largura proporcional aos dias
{/* Month headers */}
Funcionário
{months.map((m, i) => (
0 ? '1px solid var(--line-1)' : 'none' }}>{m}
))}
{/* Rows */}
{ferias.filter(f => f.gozo).map(f => {
const emp = EMPLOYEES.find(e => e.id === f.empId);
const g = parseGozo(f.gozo);
const st = statusMeta[f.status];
const startPct = ((g.ini.m * 30 + g.ini.d) / 360) * 100;
const widthPct = (f.dias / 360) * 100;
return (
{emp.nome.split(' ').slice(0, 2).join(' ')}
{f.dias}d · {f.abono > 0 ? `+${f.abono}d abono` : 'gozo total'}
{months.map((_, i) =>
0 ? '1px solid var(--line-1)' : 'none' }}/>)}
{f.dias}d
);
})}
{/* Table - all periods */}
| Funcionário |
Período aquisitivo |
Limite gozo |
Gozo programado |
Dias |
Abono |
Status |
Ações |
{ferias.map(f => {
const emp = EMPLOYEES.find(e => e.id === f.empId);
const st = statusMeta[f.status];
return (
{emp.nome.split(' ').slice(0, 2).join(' ')}
{emp.cargo}
|
{f.aquisitivo} |
{f.limite} |
{f.gozo || '— não programado —'} |
{f.dias} |
0 ? 'var(--text-1)' : 'var(--text-4)' }}>{f.abono > 0 ? `${f.abono}d` : '—'} |
{st.l} |
{f.status === 'a_programar' || f.status === 'vence_em_breve' || f.status === 'vencida' ?
:
}
|
);
})}
);
};
// =============================================================
// 3. Licenças
// =============================================================
const PageLicencas = ({ onNavigate }) => {
const tipos = [
{ id: 'maternidade', l: 'Maternidade', bg: 'rgba(236,72,153,.16)', c: '#f9a8d4', i: 'heart', dias: 120 },
{ id: 'paternidade', l: 'Paternidade', bg: 'rgba(59,130,246,.14)', c: '#93c5fd', i: 'heart', dias: 5 },
{ id: 'medica', l: 'Médica (INSS)', bg: 'rgba(34,197,94,.14)', c: '#86efac', i: 'heartbeat', dias: null },
{ id: 'casamento', l: 'Casamento', bg: 'rgba(245,158,11,.14)', c: '#fcd34d', i: 'heart', dias: 3 },
{ id: 'luto', l: 'Luto', bg: 'rgba(107,114,128,.2)', c: '#d1d5db', i: 'heart', dias: 2 },
{ id: 'doacao', l: 'Doação de sangue', bg: 'rgba(239,68,68,.14)', c: '#fca5a5', i: 'heartbeat', dias: 1 },
];
const licencas = [
{ id: 1, empId: 11, tipo: 'maternidade', inicio: '08/04/2026', fim: '05/08/2026', dias: 120, restante: 110, motivo: 'Nascimento - Beatriz Oliveira', doc: 'Certidão nasc. enviada', status: 'ativa', salarioInss: true },
{ id: 2, empId: 5, tipo: 'medica', inicio: '12/04/2026', fim: '19/04/2026', dias: 8, restante: 3, motivo: 'Cirurgia apendicite', doc: 'Atestado CID K35.8', status: 'ativa', salarioInss: false },
{ id: 3, empId: 4, tipo: 'doacao', inicio: '10/04/2026', fim: '10/04/2026', dias: 1, restante: 0, motivo: 'Hemominas · comprovante', doc: 'Comprovante', status: 'concluida', salarioInss: false },
{ id: 4, empId: 6, tipo: 'casamento', inicio: '02/06/2026', fim: '04/06/2026', dias: 3, restante: null, motivo: 'Casamento civil', doc: 'Certidão', status: 'programada', salarioInss: false },
{ id: 5, empId: 15, tipo: 'luto', inicio: '18/03/2026', fim: '19/03/2026', dias: 2, restante: 0, motivo: 'Falecimento avô', doc: 'Atestado óbito', status: 'concluida', salarioInss: false },
{ id: 6, empId: 8, tipo: 'paternidade', inicio: '22/02/2026', fim: '26/02/2026', dias: 5, restante: 0, motivo: 'Nascimento Theo', doc: 'Certidão nasc.', status: 'concluida', salarioInss: false },
{ id: 7, empId: 12, tipo: 'medica', inicio: '01/04/2026', fim: '05/04/2026', dias: 5, restante: 0, motivo: 'Gripe/bronquite', doc: 'Atestado CID J40', status: 'concluida', salarioInss: false },
];
const ativas = licencas.filter(l => l.status === 'ativa');
return (
>}
>
{/* Types row */}
Tipos de licença (CLT)
{tipos.map(t => (
{t.l}
{t.dias ? `Até ${t.dias} dias` : 'Conforme atestado'}
))}
{/* Active licenses cards */}
{ativas.length > 0 && <>
Licenças em andamento
{ativas.map(l => {
const emp = EMPLOYEES.find(e => e.id === l.empId);
const t = tipos.find(t => t.id === l.tipo);
const pct = ((l.dias - l.restante) / l.dias) * 100;
return (
{l.motivo}
{l.inicio} → {l.fim}
Dia {l.dias - l.restante} de {l.dias}
{l.restante} dias restantes
{l.doc}
{l.salarioInss &&
Reembolso INSS
}
);
})}
>}
Histórico · últimos 12 meses
| Funcionário |
Tipo |
Início |
Fim |
Dias |
Motivo / documento |
Status |
{licencas.map(l => {
const emp = EMPLOYEES.find(e => e.id === l.empId);
const t = tipos.find(t => t.id === l.tipo);
return (
{emp.nome.split(' ').slice(0, 2).join(' ')}
|
{t.l} |
{l.inicio} |
{l.fim} |
{l.dias} |
{l.motivo}
{l.doc}
|
{l.status === 'ativa' ? 'Ativa' : l.status === 'programada' ? 'Programada' : 'Concluída'} |
);
})}
);
};
// =============================================================
// 4. Tipos de Folga (cadastro)
// =============================================================
const PageTiposFolga = ({ onNavigate }) => {
const [editing, setEditing] = React.useState(null);
const tipos = [
{ id: 1, nome: 'Folga compensatória', codigo: 'FC', dias: 1, remunerada: true, exige_aprov: true, antecedencia: 3, doc: false, cor: '#3b82f6', usos: 48, desc: 'Compensação por horas extras trabalhadas em dias úteis ou feriados' },
{ id: 2, nome: 'Folga aniversário', codigo: 'FA', dias: 1, remunerada: true, exige_aprov: false, antecedencia: 15, doc: false, cor: '#ec4899', usos: 17, desc: 'Benefício da empresa · uso no mês de aniversário', ativo: true },
{ id: 3, nome: 'Falta justificada', codigo: 'FJ', dias: null, remunerada: true, exige_aprov: true, antecedencia: 0, doc: true, cor: '#f59e0b', usos: 12, desc: 'Ausência com motivo válido e documentação (atestado, comprovante)' },
{ id: 4, nome: 'Falta abonada', codigo: 'FAB', dias: 1, remunerada: true, exige_aprov: true, antecedencia: 0, doc: false, cor: '#8b5cf6', usos: 8, desc: 'Abono gerencial · limite 2 por ano por funcionário' },
{ id: 5, nome: 'Consulta médica', codigo: 'CM', dias: 0.5, remunerada: true, exige_aprov: false, antecedencia: 1, doc: true, cor: '#22c55e', usos: 31, desc: 'Saída parcial para consulta com atestado de comparecimento' },
{ id: 6, nome: 'Dispensa (banco horas)', codigo: 'DBH', dias: 1, remunerada: true, exige_aprov: true, antecedencia: 7, doc: false, cor: '#06b6d4', usos: 22, desc: 'Uso de saldo positivo de banco de horas para dispensa' },
{ id: 7, nome: 'Falta injustificada', codigo: 'FI', dias: null, remunerada: false, exige_aprov: false, antecedencia: 0, doc: false, cor: '#ef4444', usos: 3, desc: 'Ausência sem justificativa · desconta salário e DSR' },
];
return (
>}
>
t + (x.usos > 10 ? Math.round(x.usos/6) : 1), 0)} sub="total solicitações"/>
| Tipo |
Código |
Dias |
Remunerada |
Aprovação |
Antecedência |
Documento |
Usos 12m |
Ações |
{tipos.map(t => (
|
|
{t.codigo} |
{t.dias === null ? 'Variável' : t.dias === 0.5 ? '½ dia' : `${t.dias} dia${t.dias > 1 ? 's' : ''}`} |
{t.remunerada ?
:
}
|
{t.exige_aprov ?
Obrigatória :
Automática}
|
{t.antecedencia === 0 ? 'Imediata' : `${t.antecedencia} dias`} |
{t.doc ?
Necessário :
—}
|
{t.usos} |
|
))}
{editing && setEditing(null)}/>}
);
};
const TipoFolgaDrawer = ({ tipo, onClose }) => {
const isNew = tipo.novo;
return (
<>
{isNew ? 'Novo tipo de folga' : 'Editar tipo'}
{[
['remunerada', 'Remunerada', 'Desconta ou não do salário', tipo.remunerada ?? true],
['exige_aprov', 'Exige aprovação do gerente', 'Solicitação vai para fluxo de aprovação', tipo.exige_aprov ?? true],
['doc', 'Exige documento comprobatório', 'Ex: atestado médico, comprovante', tipo.doc ?? false],
['desconta_dsr', 'Desconta DSR', 'Afeta o descanso semanal remunerado', false],
].map(([id, l, sub, checked]) => (
))}
>
);
};
const Field = ({ label, children }) => (
);
// =============================================================
// 5. Alocações
// =============================================================
const PageAlocacoes = ({ onNavigate }) => {
// Alocações: funcionário → centro de custo / loja / projeto com % de tempo
const alocacoes = [
{ empId: 1, items: [{ loc: 'Matriz · Gestão', pct: 100, tipo: 'admin' }] },
{ empId: 3, items: [{ loc: 'Matriz · Produção', pct: 70, tipo: 'producao' }, { loc: 'Filial Savassi · Produção', pct: 30, tipo: 'producao' }] },
{ empId: 4, items: [{ loc: 'Filial Savassi · Loja', pct: 100, tipo: 'vendas' }] },
{ empId: 5, items: [{ loc: 'Matriz · Produção', pct: 100, tipo: 'producao' }] },
{ empId: 6, items: [{ loc: 'Matriz · Produção', pct: 80, tipo: 'producao' }, { loc: 'Filial Savassi · Produção', pct: 20, tipo: 'producao' }] },
{ empId: 7, items: [{ loc: 'Filial Savassi · Loja', pct: 100, tipo: 'vendas' }] },
{ empId: 10, items: [{ loc: 'Logística · Matriz', pct: 60, tipo: 'logistica' }, { loc: 'Logística · Filial', pct: 40, tipo: 'logistica' }] },
{ empId: 13, items: [{ loc: 'Filial Savassi · Supervisão', pct: 100, tipo: 'vendas' }] },
{ empId: 16, items: [{ loc: 'Matriz · RH', pct: 100, tipo: 'admin' }] },
{ empId: 2, items: [{ loc: 'Matriz · Vendas', pct: 50, tipo: 'vendas' }, { loc: 'Projeto · B2B Restaurantes', pct: 50, tipo: 'projeto' }] },
];
const tipoColor = {
admin: '#3b82f6',
producao: '#f59e0b',
vendas: '#22c55e',
logistica: '#06b6d4',
projeto: '#8b5cf6',
};
const centros = [
{ nome: 'Matriz · Cristiano Machado', func: 11, custo: 38400, cor: '#3b82f6' },
{ nome: 'Filial Savassi', func: 5, custo: 14800, cor: '#8b5cf6' },
{ nome: 'Projeto B2B Restaurantes', func: 1, custo: 1900, cor: '#f59e0b' },
];
return (
>}
>
a.items.length > 1).length} com alocação mista`}/>
{/* Centro de custo cards */}
Distribuição por centro de custo
{centros.map((c, i) => (
{c.nome}
{c.func} funcionários
Custo mensal
{BRL(c.custo)}
% do total
{((c.custo / 55100) * 100).toFixed(0)}%
))}
{/* Allocation table with split bars */}
Alocações por funcionário
{Object.entries(tipoColor).map(([k, v]) => (
{k}
))}
{alocacoes.map((a, i) => {
const emp = EMPLOYEES.find(e => e.id === a.empId);
return (
{a.items.map((it, j) => (
0 ? '1px solid var(--bg-2)' : 'none',
display: 'flex', alignItems: 'center', paddingLeft: 10, gap: 8, minWidth: 0,
}}>
{it.loc}
{it.pct}%
))}
{a.items.length > 1 ?
Mista :
Única}
);
})}
);
};
// =============================================================
// 6. Relatórios RH
// =============================================================
const PageRHRelatorios = ({ onNavigate }) => {
const [selected, setSelected] = React.useState('headcount');
const reports = [
{ id: 'headcount', cat: 'Gestão', nome: 'Headcount & Turnover', desc: 'Evolução do quadro · admissões, desligamentos, turnover mensal', i: 'users' },
{ id: 'folha-mensal', cat: 'Folha', nome: 'Folha mensal resumida', desc: 'Proventos, descontos, encargos por centro de custo', i: 'wallet' },
{ id: 'folha-cc', cat: 'Folha', nome: 'Folha por centro de custo', desc: 'Rateio de custo de pessoal por loja/projeto', i: 'bar-chart' },
{ id: 'ferias-vencer', cat: 'Gestão', nome: 'Férias a vencer', desc: 'Funcionários com vencimento em 90 dias', i: 'calendar' },
{ id: 'absenteismo', cat: 'Gestão', nome: 'Absenteísmo', desc: 'Faltas, atestados, licenças por período', i: 'alert' },
{ id: 'ponto-divergencias', cat: 'Ponto', nome: 'Divergências de ponto', desc: 'Atrasos, faltas, horas extras não aprovadas', i: 'clock' },
{ id: 'inss', cat: 'Obrigações', nome: 'GPS · INSS', desc: 'Guia de previdência social para pagamento', i: 'doc' },
{ id: 'fgts', cat: 'Obrigações', nome: 'GRRF · FGTS', desc: 'Guia de recolhimento FGTS mensal', i: 'doc' },
{ id: 'rais', cat: 'Obrigações', nome: 'RAIS anual', desc: 'Relação anual de informações sociais', i: 'doc' },
{ id: 'dirf', cat: 'Obrigações', nome: 'DIRF', desc: 'Declaração de imposto retido na fonte', i: 'doc' },
{ id: 'comissoes', cat: 'Vendas', nome: 'Comissões apuradas', desc: 'Histórico de comissões por vendedor', i: 'trending-up' },
{ id: 'ajustes', cat: 'Gestão', nome: 'Ajustes salariais', desc: 'Histórico de mérito, promoção, dissídio', i: 'award' },
];
const cats = [...new Set(reports.map(r => r.cat))];
const filtered = reports;
const sel = reports.find(r => r.id === selected);
// Preview data for headcount
const hcByDept = [
{ d: 'Produção', count: 7, salario: 18120 },
{ d: 'Vendas', count: 4, salario: 8520 },
{ d: 'Administrativo', count: 3, salario: 13300 },
{ d: 'Comercial', count: 2, salario: 6000 },
{ d: 'Logística', count: 1, salario: 2600 },
{ d: 'Diretoria', count: 1, salario: 9200 },
];
return (
>}
>
{/* Reports list */}
{cats.map(cat => (
{cat}
{filtered.filter(r => r.cat === cat).map(r => (
))}
))}
{/* Preview */}
{selected === 'headcount' && <>
Evolução headcount últimos 12 meses
Distribuição por departamento
{hcByDept.map(d => (
{d.d}
{d.count} pessoas
{BRL(d.salario)}
))}
>}
{selected !== 'headcount' &&
{sel.nome}
Relatório pronto para geração. Use os botões acima para exportar em PDF, Excel ou CSV.
}
);
};
const MiniKPI = ({ label, value, sub }) => (
);
Object.assign(window, { PageComissoes, PageFerias, PageLicencas, PageTiposFolga, PageAlocacoes, PageRHRelatorios });