// Wave 3b — RH (6 telas): Comissões, Férias, Licenças, Tipos de Folga, Alocações, Relatórios RH // ============================================================= // 1. Comissões // ============================================================= const PageComissoes = ({ onNavigate }) => { const vendedores = EMPLOYEES.filter(e => ['Comercial', 'Vendas'].includes(e.depto)); const comissoes = vendedores.map((v, i) => { const vendas = [142800, 98420, 76200, 89100, 124500, 58900, 112340][i] || 50000; const meta = [130000, 90000, 80000, 85000, 110000, 65000, 100000][i] || 60000; const pct = (vendas / meta) * 100; const taxa = pct >= 120 ? 0.05 : pct >= 100 ? 0.035 : pct >= 80 ? 0.025 : 0.015; const comissao = vendas * taxa; const bonusMeta = pct >= 100 ? 500 : 0; return { ...v, vendas, meta, pct, taxa, comissao, bonusMeta, total: comissao + bonusMeta }; }).sort((a, b) => b.vendas - a.vendas); const totais = comissoes.reduce((t, c) => ({ vendas: t.vendas + c.vendas, comissao: t.comissao + c.comissao, bonus: t.bonus + c.bonusMeta, }), { vendas: 0, comissao: 0, bonus: 0 }); return ( } >
c.bonusMeta).length} vendedores`}/> c.pct >= 100).length} bateram meta`}/>
{/* Regra escalonada */}
Regra vigente · escalonado por atingimento
{[ { range: '< 80%', taxa: '1,5%', c: '#fca5a5', bg: 'rgba(239,68,68,.08)' }, { range: '80 – 99%', taxa: '2,5%', c: '#fcd34d', bg: 'rgba(245,158,11,.08)' }, { range: '100 – 119%', taxa: '3,5%', c: '#86efac', bg: 'rgba(34,197,94,.08)' }, { range: '≥ 120%', taxa: '5,0% + R$500', c: '#c4b5fd', bg: 'rgba(139,92,246,.10)' }, ].map((r, i) => (
Atingimento
{r.range}
Comissão: {r.taxa}
))}
{comissoes.map((c, i) => ( ))}
Vendedor Vendas Meta Atingimento Taxa Comissão Bônus Total
{i + 1}
{c.nome}
{c.cargo}
{BRL(c.vendas)} {BRL(c.meta)}
= 120 ? 'linear-gradient(90deg, #a78bfa, #c4b5fd)' : c.pct >= 100 ? 'linear-gradient(90deg, #22c55e, #86efac)' : c.pct >= 80 ? '#f59e0b' : '#ef4444', }}/>
= 100 ? '#86efac' : 'var(--text-2)', minWidth: 44, textAlign: 'right' }}>{c.pct.toFixed(0)}%
{(c.taxa * 100).toFixed(1)}% {BRL(c.comissao)} 0 ? '#c4b5fd' : 'var(--text-4)' }}>{c.bonusMeta > 0 ? BRL(c.bonusMeta) : '—'} {BRL(c.total)}
TOTAIS {BRL(totais.vendas)} {BRL(totais.bonus)} {BRL(totais.comissao + totais.bonus)}
); }; // ============================================================= // 2. Férias // ============================================================= const PageFerias = ({ onNavigate }) => { const ferias = [ { id: 1, empId: 7, periodo: '2024/2025', aquisitivo: '27/02/24 – 26/02/25', limite: '26/02/26', gozo: '01/04/26 – 30/04/26', dias: 30, status: 'em_gozo', abono: 0, avisoPrev: '13/03/26' }, { id: 2, empId: 11, periodo: '2024/2025', aquisitivo: '12/05/24 – 11/05/25', limite: '11/05/26', gozo: '15/06/26 – 04/07/26', dias: 20, status: 'programada', abono: 10, avisoPrev: '27/05/26' }, { id: 3, empId: 2, periodo: '2024/2025', aquisitivo: '05/06/24 – 04/06/25', limite: '04/06/26', gozo: null, dias: 30, status: 'vence_em_breve', abono: 0, avisoPrev: null }, { id: 4, empId: 13, periodo: '2024/2025', aquisitivo: '07/01/24 – 06/01/25', limite: '06/01/26', gozo: null, dias: 30, status: 'vencida', abono: 0, avisoPrev: null }, { id: 5, empId: 1, periodo: '2025/2026', aquisitivo: '12/03/25 – 11/03/26', limite: '11/03/27', gozo: '14/07/26 – 02/08/26', dias: 20, status: 'programada', abono: 10, avisoPrev: '25/06/26' }, { id: 6, empId: 3, periodo: '2025/2026', aquisitivo: '22/01/25 – 21/01/26', limite: '21/01/27', gozo: null, dias: 30, status: 'a_programar', abono: 0, avisoPrev: null }, { id: 7, empId: 6, periodo: '2024/2025', aquisitivo: '18/04/24 – 17/04/25', limite: '17/04/26', gozo: '20/04/26 – 09/05/26', dias: 20, status: 'aprovada', abono: 10, avisoPrev: '01/04/26' }, { id: 8, empId: 10, periodo: '2024/2025', aquisitivo: '01/10/24 – 30/09/25', limite: '30/09/26', gozo: null, dias: 30, status: 'a_programar', abono: 0, avisoPrev: null }, { id: 9, empId: 12, periodo: '2024/2025', aquisitivo: '23/06/24 – 22/06/25', limite: '22/06/26', gozo: '10/08/26 – 29/08/26', dias: 20, status: 'programada', abono: 10, avisoPrev: '21/07/26' }, ]; const statusMeta = { em_gozo: { l: 'Em gozo', bg: 'rgba(59,130,246,.16)', c: '#93c5fd' }, programada: { l: 'Programada', bg: 'rgba(139,92,246,.14)', c: '#c4b5fd' }, aprovada: { l: 'Aprovada', bg: 'rgba(34,197,94,.14)', c: '#86efac' }, a_programar: { l: 'A programar', bg: 'rgba(245,158,11,.14)', c: '#fcd34d' }, vence_em_breve: { l: 'Vence em breve', bg: 'rgba(245,158,11,.2)', c: '#fbbf24' }, vencida: { l: 'VENCIDA', bg: 'rgba(239,68,68,.18)', c: '#fca5a5' }, }; // Timeline 2026 const months = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez']; const parseGozo = (s) => { if (!s) return null; const [ini, fim] = s.split(' – '); const parse = (d) => { const [dd, mm] = d.split('/'); return { d: +dd, m: +mm - 1 }; }; return { ini: parse(ini), fim: parse(fim) }; }; return ( } >
f.status === 'em_gozo').length} sub="1 pessoa · abril/26"/> f.status === 'vencida').length} sub="Mariana · janeiro/26" accent="rgba(239,68,68,.2)"/> f.status === 'vence_em_breve').length} sub="Bruno · junho/26" accent="rgba(245,158,11,.2)"/>
{/* Timeline calendar 2026 */}
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 */}
Períodos aquisitivos
{ferias.map(f => { const emp = EMPLOYEES.find(e => e.id === f.empId); const st = statusMeta[f.status]; return ( ); })}
Funcionário Período aquisitivo Limite gozo Gozo programado Dias Abono Status Ações
{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 (
{emp.nome}
{t.l}
{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
{licencas.map(l => { const emp = EMPLOYEES.find(e => e.id === l.empId); const t = tipos.find(t => t.id === l.tipo); return ( ); })}
Funcionário Tipo Início Fim Dias Motivo / documento Status
{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"/>
{tipos.map(t => ( ))}
Tipo Código Dias Remunerada Aprovação Antecedência Documento Usos 12m Ações
{t.nome}
{t.desc}
{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'}

{['#3b82f6', '#8b5cf6', '#ec4899', '#f59e0b', '#22c55e', '#06b6d4', '#ef4444'].map(c => (