// 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 (
{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' && (
| Projeto |
Cliente |
Progresso |
Equipe |
Prazo |
Orçamento |
|
{PROJETOS.map(p => (
setSelected(p)} style={{ borderTop: '1px solid var(--line-1)', cursor: 'pointer' }}>
|
{p.nome}
{p.id}
|
{p.cliente} |
|
{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 => (
))}
{['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 (
{['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 => (
))}
{['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' && (
| Data |
Colaborador |
Horas |
Descrição |
{timesheet.map((t, i) => (
| {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 => (
))}
)}
>
);
};
// =============================================================
// 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
| OS |
Cliente · Serviço |
Técnico |
Agendamento |
Status |
SLA |
Valor |
{filtered.map(o => (
setSelected(o)} style={{ borderTop: '1px solid var(--line-1)', cursor: 'pointer' }}>
| {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 });