/* === value-tracker-kanban.jsx ======================================== Kanban board + Assistant panel for the Value Tracker. ===================================================================== */ const VT_COLS = window.VT_COLUMNS; /* ================================================================== KANBAN ================================================================== */ function VTKanban({ vt, setVT, state }) { const [dragId, setDragId] = React.useState(null); const [dropOn, setDropOn] = React.useState(null); const [editingId, setEditingId] = React.useState(null); const planosByCol = Object.fromEntries(VT_COLS.map(c => [c.id, vt.planos.filter(p => p.column === c.id)])); const updatePlano = (id, patch) => { setVT(curr => ({ ...curr, planos: curr.planos.map(p => p.id === id ? { ...p, ...patch } : p) })); }; const removePlano = (id) => { setVT(curr => ({ ...curr, planos: curr.planos.filter(p => p.id !== id) })); }; const addPlano = (column) => { const id = 'p' + Date.now().toString(36); const newPlan = { id, title: 'Novo plano', priority: 'media', dueDate: new Date().toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric' }), owner: 'Responsável', column, }; setVT(curr => ({ ...curr, planos: [...curr.planos, newPlan] })); setEditingId(id); }; return (
Planos de Ação {vt.planos.length} planos
{VT_COLS.map(col => { const items = planosByCol[col.id] || []; return (
{ e.preventDefault(); setDropOn(col.id); }} onDragLeave={() => setDropOn(null)} onDrop={(e) => { e.preventDefault(); if (dragId) updatePlano(dragId, { column: col.id }); setDragId(null); setDropOn(null); }} >
{col.label} {items.length}
{items.map(plan => ( setEditingId(plan.id)} onFinishEdit={() => setEditingId(null)} onChange={(patch) => updatePlano(plan.id, patch)} onRemove={() => removePlano(plan.id)} onDragStart={() => setDragId(plan.id)} onDragEnd={() => setDragId(null)} /> ))}
); })}
); } function PlanCard({ plan, editing, onStartEdit, onFinishEdit, onChange, onRemove, onDragStart, onDragEnd }) { const priColor = { alta: '#EF4444', media: '#F59E0B', baixa: '#9CA3AF' }[plan.priority] || '#9CA3AF'; const priLabel = { alta: 'Alta', media: 'Média', baixa: 'Baixa' }[plan.priority] || plan.priority; if (editing) { return (
e.stopPropagation()}> onChange({ title: e.target.value })} onKeyDown={(e) => { if (e.key === 'Enter') onFinishEdit(); }} placeholder="Título do plano" />
onChange({ dueDate: e.target.value })} placeholder="DD mes 2026" />
onChange({ owner: e.target.value })} placeholder="Responsável" />
); } return (
{priLabel}
); } /* ================================================================== ASSISTANT PANEL ================================================================== */ function VTAssistant({ vt, setVT, onCollapse }) { const [input, setInput] = React.useState(''); const messages = vt.assistantMessages || []; const bottomRef = React.useRef(null); React.useEffect(() => { bottomRef.current?.scrollIntoView({ block: 'end' }); }, [messages.length]); const send = (text, opts) => { if (!text.trim() && !opts) return; const userMsg = { role: 'user', text, ts: Date.now() }; let botMsg = null; if (opts?.action === 'criar-plano') { botMsg = { role: 'assistant', kind: 'plano-wizard', text: 'Perfeito! Vou te guiar para criar um plano estruturado. Responda estas 5 perguntas:', fields: [ { key: 'titulo', label: 'Título', hint: 'O que você quer fazer? (ex: "Reduzir tempo de processamento de pedidos")' }, { key: 'onde', label: 'Onde será aplicado', hint: 'Em qual área/processo? (ex: "Departamento de Logística", "Sistema de Vendas")' }, { key: 'como', label: 'Como será executado', hint: 'Qual é a estratégia/método? (ex: "Implementar automação", "Treinar equipe", "Revisar processos")' }, { key: 'responsavel', label: 'Responsável', hint: 'Quem vai liderar? (nome da pessoa)' }, { key: 'prazo', label: 'Prazo', hint: 'Quando termina? (data no formato: DD/MM/YYYY ou mês/ano, ex: "31/03/2026")' }, ], }; } else if (opts?.action === 'registrar') { botMsg = { role: 'assistant', text: 'Vamos registrar um ganho! Para começar: qual o **título do ganho** (ex: "Consolidação de Compras Spot em Contrato") e o **valor estimado** (em R$)?', }; } else if (opts?.action === 'potencial') { botMsg = { role: 'assistant', text: 'Analisando os 17 ganhos em **Identificado** (R$ 7,5M), os 3 com maior potencial de conversão rápida são:\n\n1. **Consolidação de Compras Spot em Contrato** — saving de ~12% em latão (Wieland), maturidade alta no fornecedor.\n2. **Automação de aprovação de POs < R$ 50k** — 380 POs/mês, redução de 4h por PO.\n3. **DPO estendido em MRO para 90 dias** — R$ 1,2M em capital de giro liberado, requer apenas renegociação contratual.', }; } else if (opts?.action === 'comparar') { botMsg = { role: 'assistant', text: '**vs. trimestre anterior**:\n\n• Pipeline cresceu de R$ 38,4M → **R$ 50,2M** (+30,7%)\n• Conversão caiu de 12% → **5%** — execução tem sido o gargalo, não a identificação\n• Planos em backlog dobraram (4 → 11). Vale priorizar uma sprint de execução vs. seguir identificando.', }; } else if (opts?.action === 'oportunidades') { botMsg = { role: 'assistant', text: 'Encontrei **3 novas oportunidades** que ainda não estão no pipeline:\n\n1. **Compras parceladas em capex (~R$ 8M)** — fragmentação alta, oportunidade de bundling.\n2. **Frete inbound · região Sul** — 14% acima do benchmark do setor.\n3. **MRO recorrente sem contrato** — 240 SKUs comprados spot 6+ vezes/ano.\n\nQuer que eu transforme alguma destas em plano de ação?', }; } else { botMsg = { role: 'assistant', text: 'Bom ponto. Para responder com precisão, preciso aprofundar nos dados do pipeline. Posso sugerir começar criando um plano estruturado, ou analisar uma categoria específica do dashboard.', }; } setVT(curr => ({ ...curr, assistantMessages: [...(curr.assistantMessages || []), userMsg, botMsg] })); setInput(''); }; const submitWizard = (fields) => { const id = 'p' + Date.now().toString(36); const today = new Date().toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric' }); const newPlan = { id, title: fields.titulo || 'Novo plano (sem título)', priority: 'media', dueDate: fields.prazo || today, owner: fields.responsavel || 'Responsável', column: 'backlog', }; setVT(curr => ({ ...curr, planos: [...curr.planos, newPlan], assistantMessages: [...(curr.assistantMessages || []), { role: 'assistant', text: `✓ Plano **"${newPlan.title}"** criado no Backlog. Responsável: ${newPlan.owner}, prazo ${newPlan.dueDate}. Você pode arrastá-lo para "Em Andamento" quando começar a executar.`, }], })); }; return ( ); } function AssistantMessage({ msg, onSubmitWizard }) { if (msg.role === 'user') { return (
{msg.text}
); } if (msg.kind === 'plano-wizard') { return ; } return (
); } function WizardMessage({ msg, onSubmit }) { const [fields, setFields] = React.useState({}); const [submitted, setSubmitted] = React.useState(false); const handle = (e) => { e.preventDefault(); onSubmit(fields); setSubmitted(true); }; if (submitted) { return (
✓ Plano criado. Olhe o Kanban à esquerda.
); } return (
{msg.fields.map((f, i) => ( ))}
); } Object.assign(window, { VTKanban, VTAssistant, PlanCard });