// Wave 5a — Projetos & OS (2 telas) // ============================================================= // 1. Projetos — Kanban / Lista / Gantt / Calendário // ============================================================= const PROJETOS = [ { id: 'PRJ-042', nome: 'Novo site institucional · v3', cliente: 'Bighorse Studio', status: 'em-andamento', prog: 72, prazo: '28/05', prazoDias: 12, equipe: ['AS', 'BO', 'CM'], tarefas: 28, done: 20, sprint: 'Sprint 14', orcamento: 45000, consumido: 32400, tag: 'Web', color: '#8b5cf6' }, { id: 'PRJ-041', nome: 'Integração ERP → NF-e SEFAZ-MG', cliente: 'Padaria Pão Dourado', status: 'em-andamento', prog: 55, prazo: '10/06', prazoDias: 25, equipe: ['DF', 'ES'], tarefas: 42, done: 23, sprint: 'Sprint 7', orcamento: 28000, consumido: 15400, tag: 'Fiscal', color: '#06b6d4' }, { id: 'PRJ-040', nome: 'App mobile · cardápio digital', cliente: 'Restaurante Xapuri', status: 'atrasado', prog: 38, prazo: '20/04', prazoDias: -5, equipe: ['FC', 'GA', 'HM'], tarefas: 54, done: 21, sprint: 'Sprint 11', orcamento: 62000, consumido: 58900, tag: 'Mobile', color: '#ef4444' }, { id: 'PRJ-039', nome: 'Dashboard executivo · Looker', cliente: 'Grupo Três Marias', status: 'em-andamento', prog: 84, prazo: '30/04', prazoDias: 5, equipe: ['IF'], tarefas: 18, done: 15, sprint: 'Sprint 6', orcamento: 18000, consumido: 14200, tag: 'BI', color: '#10b981' }, { id: 'PRJ-038', nome: 'Migração Cloud AWS · produção', cliente: 'Bighorse Studio', status: 'em-andamento', prog: 22, prazo: '15/07', prazoDias: 58, equipe: ['JB', 'KL', 'LM'], tarefas: 96, done: 21, sprint: 'Sprint 3', orcamento: 120000, consumido: 18600, tag: 'DevOps', color: '#f59e0b' }, { id: 'PRJ-037', nome: 'Redesign do app de delivery', cliente: 'Padaria Pão Dourado', status: 'revisao', prog: 95, prazo: '22/04', prazoDias: 2, equipe: ['MP', 'NR'], tarefas: 34, done: 33, sprint: 'Sprint 9', orcamento: 52000, consumido: 49800, tag: 'Design', color: '#ec4899' }, { id: 'PRJ-036', nome: 'Automação fluxo de caixa', cliente: 'Grupo Três Marias', status: 'concluido', prog: 100, prazo: '15/04', prazoDias: -3, equipe: ['OS', 'PR'], tarefas: 22, done: 22, sprint: 'Finalizado', orcamento: 22000, consumido: 21400, tag: 'Finanças', color: '#22c55e' }, { id: 'PRJ-035', nome: 'Refactor sistema de pedidos', cliente: 'Restaurante Xapuri', status: 'planejamento', prog: 8, prazo: '30/08', prazoDias: 100, equipe: ['QS'], tarefas: 12, done: 1, sprint: 'Backlog', orcamento: 38000, consumido: 1200, tag: 'Web', color: '#64748b' }, ]; const PageProjetos = ({ onNavigate }) => { const [view, setView] = React.useState('kanban'); const [selected, setSelected] = React.useState(null); const cols = [ { id: 'planejamento', label: 'Planejamento', color: '#64748b' }, { id: 'em-andamento', label: 'Em andamento', color: 'var(--accent-1)' }, { id: 'revisao', label: 'Em revisão', color: '#f59e0b' }, { id: 'concluido', label: 'Concluído', color: '#22c55e' }, ]; const atrasados = PROJETOS.filter(p => p.status === 'atrasado'); return ( } >
p.status !== 'concluido' && p.status !== 'atrasado').length} sub={`${PROJETOS.length} no total`}/> p.status !== 'concluido').reduce((a, p) => a + p.prog, 0) / PROJETOS.filter(p => p.status !== 'concluido').length) + '%'} sub="projetos em aberto"/>
{[ { id: 'kanban', l: 'Kanban', i: 'grid' }, { id: 'lista', l: 'Lista', i: 'list' }, { id: 'gantt', l: 'Gantt', i: 'bar-chart' }, { id: 'calendario', l: 'Calendário', i: 'calendar' }, ].map(v => ( ))}
Agrupar por:
{view === 'kanban' && (
{cols.map(c => { const items = PROJETOS.filter(p => p.status === c.id || (c.id === 'em-andamento' && p.status === 'atrasado')); return (
{c.label} {items.length}
{items.map(p => (
setSelected(p)} style={{ background: 'var(--bg-2)', border: '1px solid var(--line-2)', borderRadius: 11, padding: 12, cursor: 'pointer', borderLeft: `3px solid ${p.color}`, }}>
{p.id} {p.status === 'atrasado' && ATRASADO}
{p.nome}
{p.cliente}
{p.equipe.map((a, i) => ( {a} ))}
{p.prazo} {p.done}/{p.tarefas}
))}
); })}
)} {view === 'lista' && (
{PROJETOS.map(p => ( setSelected(p)} style={{ borderTop: '1px solid var(--line-1)', cursor: 'pointer' }}> ))}
Projeto Cliente Progresso Equipe Prazo Orçamento
{p.nome}
{p.id}
{p.cliente}
{p.prog}%
{p.equipe.map((a, i) => ( {a} ))}
{p.prazo} · {p.prazoDias < 0 ? Math.abs(p.prazoDias) + 'd atrasado' : p.prazoDias + 'd'}
R$ {(p.consumido / 1000).toFixed(1)}k / {(p.orcamento / 1000).toFixed(0)}k
0.9 ? '#fca5a5' : 'var(--text-4)' }}>{Math.round(p.consumido / p.orcamento * 100)}% usado
)} {view === 'gantt' && (
Projeto
{PROJETOS.map(p => (
{p.nome}
{p.cliente}
))}
{['abr', 'mai', 'jun', 'jul', 'ago', 'set'].map(m => (
{m}/2026
))}
{PROJETOS.map((p, idx) => { const start = [5, 15, -10, 0, 20, 8, -20, 40][idx]; const dur = [48, 60, 45, 30, 110, 15, 25, 80][idx]; return (
{p.prog}% · {p.done}/{p.tarefas} tarefas
); })}
)} {view === 'calendario' && } {selected && setSelected(null)}/>} ); }; const ProjCalendar = ({ projetos }) => { const days = Array.from({ length: 30 }, (_, i) => i + 1); const firstDow = 1; // terça return (

Abril · 2026

{['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'].map(d => (
{d}
))} {Array.from({ length: firstDow }).map((_, i) => (
))} {days.map(d => { const due = projetos.filter(p => parseInt(p.prazo.split('/')[0]) === d && p.prazo.split('/')[1] === '04'); return (
{d}{d === 19 && ' ·'}
{due.map(p => (
Entrega · {p.nome.slice(0, 18)}…
))}
); })}
); }; const ProjDrawer = ({ projeto: p, onClose }) => { const [tab, setTab] = React.useState('tarefas'); const tarefas = [ { id: 1, nome: 'Wireframes home + institucional', responsavel: 'Ana', prazo: '20/04', status: 'done' }, { id: 2, nome: 'Revisão de copy pela Comunicação', responsavel: 'Bruno', prazo: '23/04', status: 'done' }, { id: 3, nome: 'Implementação do CMS headless', responsavel: 'Carolina', prazo: '26/04', status: 'doing' }, { id: 4, nome: 'Integração com WhatsApp Business', responsavel: 'Ana', prazo: '02/05', status: 'todo' }, { id: 5, nome: 'Testes de acessibilidade (WCAG AA)', responsavel: 'Bruno', prazo: '10/05', status: 'todo' }, { id: 6, nome: 'Deploy em staging + QA final', responsavel: 'Carolina', prazo: '20/05', status: 'todo' }, ]; const timesheet = [ { data: '16/04', colab: 'Ana', horas: 6.5, nota: 'Wireframes + ajustes' }, { data: '16/04', colab: 'Carolina', horas: 4, nota: 'Setup CMS' }, { data: '15/04', colab: 'Bruno', horas: 3, nota: 'Revisão copy' }, { data: '15/04', colab: 'Ana', horas: 7, nota: 'Design da home' }, { data: '14/04', colab: 'Carolina', horas: 8, nota: 'Arquitetura backend' }, ]; return ( <>
{p.tag.toUpperCase()}

{p.nome}

{p.cliente} · {p.id}
{[ { l: 'Progresso', v: p.prog + '%' }, { l: 'Prazo', v: p.prazo }, { l: 'Tarefas', v: `${p.done}/${p.tarefas}` }, { l: 'Orçamento', v: Math.round(p.consumido / p.orcamento * 100) + '%' }, ].map(s => (
{s.l}
{s.v}
))}
{['tarefas', 'sprints', 'timesheet', 'anexos'].map(t => ( ))}
{tab === 'tarefas' && tarefas.map(t => (
{t.status === 'done' && }
{t.nome}
{t.responsavel} · até {t.prazo}
{t.status}
))} {tab === 'sprints' && (
{['Sprint 14 · atual', 'Sprint 13', 'Sprint 12'].map((s, i) => (
{s}
{i === 0 ? '12 tarefas · 8 done · 3 doing' : '14 tarefas · 14 done · encerrada'}
))}
)} {tab === 'timesheet' && ( {timesheet.map((t, i) => ( ))}
Data Colaborador Horas Descrição
{t.data} {t.colab} {t.horas}h {t.nota}
)} {tab === 'anexos' && (
{[ { n: 'Briefing-v3.pdf', sz: '2,4 MB', d: '10/04' }, { n: 'Wireframes-home.fig', sz: '14 MB', d: '14/04' }, { n: 'Contrato-assinado.pdf', sz: '1,1 MB', d: '02/04' }, ].map(a => (
{a.n}
{a.sz} · {a.d}
))}
)}
); }; // ============================================================= // 2. Ordens de Serviço // ============================================================= const OSS = [ { id: 'OS-0421', cliente: 'Restaurante Xapuri', servico: 'Manutenção preventiva · câmara fria', tecnico: 'Carlos Mendonça', status: 'agendada', data: '22/04 · 09h', valor: 480, sla: 'ok' }, { id: 'OS-0420', cliente: 'Padaria Pão Dourado', servico: 'Instalação forno rotativo · setup inicial', tecnico: 'Marcos Silveira', status: 'em-execucao', data: '19/04 · 14h', valor: 2800, sla: 'ok' }, { id: 'OS-0419', cliente: 'Bighorse Studio', servico: 'Troca do ar-condicionado da sala de reunião', tecnico: 'Roberto Ferraz', status: 'em-execucao', data: '19/04 · 11h', valor: 1250, sla: 'atencao' }, { id: 'OS-0418', cliente: 'Grupo Três Marias', servico: 'Emergência: vazamento no chiller · refrigeração', tecnico: 'Carlos Mendonça', status: 'em-execucao', data: '19/04 · 07h', valor: 3200, sla: 'critico' }, { id: 'OS-0417', cliente: 'Restaurante Xapuri', servico: 'Limpeza química coifa + filtros (trimestral)', tecnico: 'Henrique Alves', status: 'concluida', data: '18/04 · 08h', valor: 620, sla: 'ok' }, { id: 'OS-0416', cliente: 'Padaria Pão Dourado', servico: 'Inspeção anual de gás GLP · laudo NR-13', tecnico: 'Marcos Silveira', status: 'concluida', data: '17/04 · 15h', valor: 890, sla: 'ok' }, { id: 'OS-0415', cliente: 'Café Savassi', servico: 'Reparo máquina de café expresso · rolamento', tecnico: 'Henrique Alves', status: 'concluida', data: '17/04 · 10h', valor: 340, sla: 'ok' }, { id: 'OS-0414', cliente: 'Bighorse Studio', servico: 'Pintura externa fachada · 2 demãos', tecnico: 'Time externo', status: 'concluida', data: '15/04', valor: 4800, sla: 'ok' }, { id: 'OS-0413', cliente: 'Grupo Três Marias', servico: 'Substituição compressor · OS-0398 retorno', tecnico: 'Carlos Mendonça', status: 'pendente-aprov', data: '14/04', valor: 5400, sla: 'ok' }, { id: 'OS-0412', cliente: 'Café Savassi', servico: 'Manutenção preventiva · ar-condicionado 2 unid.', tecnico: 'Roberto Ferraz', status: 'concluida', data: '12/04', valor: 720, sla: 'ok' }, ]; const PageOS = ({ onNavigate }) => { const [selected, setSelected] = React.useState(null); const [status, setStatus] = React.useState('todas'); const statusMeta = { 'agendada': { l: 'Agendada', c: '#93c5fd', bg: 'rgba(59,130,246,.14)' }, 'em-execucao': { l: 'Em execução', c: '#fcd34d', bg: 'rgba(245,158,11,.14)' }, 'concluida': { l: 'Concluída', c: '#86efac', bg: 'rgba(34,197,94,.14)' }, 'pendente-aprov': { l: 'Pendente aprov.', c: '#c4b5fd', bg: 'var(--accent-soft-14)' }, }; const slaColor = { ok: '#86efac', atencao: '#fcd34d', critico: '#fca5a5' }; const filtered = OSS.filter(o => status === 'todas' || o.status === status); const total = OSS.reduce((a, o) => a + o.valor, 0); return ( } >
o.status !== 'concluida').length} sub={`${OSS.filter(o => o.status === 'em-execucao').length} em execução`}/> o.sla === 'critico').length} sub="requer atenção imediata" deltaDir="down"/>
{[['todas', 'Todas'], ['agendada', 'Agendadas'], ['em-execucao', 'Em execução'], ['concluida', 'Concluídas'], ['pendente-aprov', 'Aprovação']].map(([v, l]) => ( ))}
{filtered.length} OS
{filtered.map(o => ( setSelected(o)} style={{ borderTop: '1px solid var(--line-1)', cursor: 'pointer' }}> ))}
OS Cliente · Serviço Técnico Agendamento Status SLA Valor
{o.id}
{o.cliente}
{o.servico}
{o.tecnico} {o.data} {statusMeta[o.status].l} R$ {o.valor.toLocaleString('pt-BR')}
{selected && setSelected(null)}/>}
); }; const OSDrawer = ({ os: o, onClose }) => { const checklist = [ { t: 'Verificação inicial do equipamento', done: true }, { t: 'Foto antes do serviço (entrada)', done: true }, { t: 'Execução da manutenção', done: o.status !== 'agendada' }, { t: 'Teste de funcionamento pós-serviço', done: o.status === 'concluida' }, { t: 'Foto depois do serviço (saída)', done: o.status === 'concluida' }, { t: 'Assinatura do cliente', done: o.status === 'concluida' }, ]; return ( <>
{o.id}

{o.servico}

{o.cliente} · {o.tecnico} · {o.data}

Checklist de execução

{checklist.map((c, i) => (
{c.done && } {c.t}
))}

Anexos de campo

{['Antes', 'Durante', 'Depois'].map((l, i) => (
{l}
))}

Assinatura do cliente

{o.status === 'concluida' ? ( <>
Assinado por {o.cliente.split(' ')[0]} · {o.data}
) : (
Pendente de assinatura digital
)}
Valor do serviço R$ {o.valor.toLocaleString('pt-BR')}
); }; Object.assign(window, { PROJETOS, OSS, PageProjetos, PageOS });