import React, { useEffect, useMemo, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { BarChart3, BookOpen, CalendarDays, CheckCircle2, ClipboardCheck, Cloud, CloudOff, Download, Fish, Flag, LayoutDashboard, Lightbulb, ListChecks, Plus, RotateCw, Save, Search, Send, Sigma, Target, Trash2, UserRoundCheck, Users, X } from 'lucide-react'; import './styles.css'; const STORAGE_KEY = 'melhoria-continua-v2'; const API_ENTRY = './api/index.php'; const statusList = [ { id: 'triagem', label: 'Triagem' }, { id: 'analise', label: 'Análise' }, { id: 'execucao', label: 'Execução' }, { id: 'validacao', label: 'Validação' }, { id: 'padronizado', label: 'Padronizado' } ]; const defaultPareto = [ { cause: 'Falha de comunicação', count: 12 }, { cause: 'Ausência de padrão', count: 9 }, { cause: 'Treinamento insuficiente', count: 6 }, { cause: 'Sistema/processo', count: 4 } ]; const defaultIshikawa = { method: ['Procedimento não padronizado'], people: ['Treinamento inconsistente'], machine: ['Ferramenta indisponível'], material: ['Informação incompleta'], measurement: ['Indicador não acompanhado'], environment: ['Mudança de prioridade no turno'] }; const defaultFiveW1H = [ { what: 'Definir padrão de execução', why: 'Reduzir variação entre turnos', who: 'Supervisor', where: 'Operação', when: today(), how: 'Criar checklist e validar no gemba', howMuch: '', status: 'Pendente' } ]; const defaultSigma = { define: 'Descrever problema, cliente impactado e meta operacional.', measure: 'Coletar dados atuais e linha de base.', analyze: 'Identificar causa raiz com evidências.', improve: 'Testar solução piloto e medir ganho.', control: 'Padronizar, treinar e acompanhar aderência.' }; const initialData = { activeModule: 'overview', activeImprovementId: null, improvements: [], actions: [], pdca: {}, tools: {}, meetings: [], requests: [] }; function today() { return new Date().toISOString().slice(0, 10); } function makeId(prefix) { return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`; } function wait(ms) { return new Promise((resolve) => { window.setTimeout(resolve, ms); }); } function apiUrl(route) { return `${API_ENTRY}?route=${encodeURIComponent(route)}`; } function storageKey(companyId) { return `${STORAGE_KEY}-${companyId}`; } function loadCompanyLocal(companyId) { try { const saved = window.localStorage.getItem(storageKey(companyId)); return saved ? normalizeData(JSON.parse(saved)) : initialData; } catch { return initialData; } } function normalizeData(data) { return { ...initialData, ...data, improvements: data?.improvements ?? [], actions: data?.actions ?? [], meetings: data?.meetings ?? [], requests: data?.requests ?? [], pdca: data?.pdca ?? {}, tools: data?.tools ?? {} }; } async function fetchJson(url, options) { const response = await fetch(url, { credentials: 'include', ...options }); const text = await response.text(); const contentType = response.headers.get('content-type') ?? ''; if (!contentType.includes('application/json')) { throw new Error('A API não respondeu JSON. Verifique o backend.'); } const payload = text ? JSON.parse(text) : {}; if (!response.ok) { throw new Error(payload.error ?? 'Falha na API.'); } return payload; } function statusLabel(status) { return statusList.find((item) => item.id === status)?.label ?? status; } function activeTools(data, id) { return data.tools[id] ?? { pareto: defaultPareto, ishikawa: defaultIshikawa, fivew1h: defaultFiveW1H, sigma: defaultSigma }; } function App() { const [data, setData] = useState(initialData); const [query, setQuery] = useState(''); const [sync, setSync] = useState({ online: false, status: 'offline', message: 'Modo offline local' }); const [isImprovementModalOpen, setImprovementModalOpen] = useState(false); const [auth, setAuth] = useState(null); const [authLoading, setAuthLoading] = useState(true); const activeImprovement = data.improvements.find((item) => item.id === data.activeImprovementId) ?? data.improvements[0] ?? null; useEffect(() => { if (!auth?.company?.id) return; window.localStorage.setItem(storageKey(auth.company.id), JSON.stringify(data)); }, [auth?.company?.id, data]); useEffect(() => { initializeSession(); }, []); useEffect(() => { if (!auth || !sync.online) return; const timer = window.setTimeout(() => { saveToServer(data, false); }, 900); return () => window.clearTimeout(timer); }, [data, sync.online]); async function initializeSession() { try { const payload = await fetchJson(apiUrl('me')); if (payload.user) { setAuth(payload.user); setData(loadCompanyLocal(payload.user.company.id)); await loadServerData(); } } catch { setAuth(null); } finally { setAuthLoading(false); } } async function login(credentials) { const payload = await fetchJson(apiUrl('login'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }); setData(loadCompanyLocal(payload.user.company.id)); setAuth(payload.user); await loadServerData(); } async function logout() { await fetchJson(apiUrl('logout'), { method: 'POST' }); setAuth(null); setData(initialData); setSync({ online: false, status: 'offline', message: 'Sessão encerrada' }); } async function loadServerData() { try { const startupSteps = [ 'Conectando ao banco de dados...', 'Checando migrations automáticas...', 'Analisando tabelas e atualizações...', 'Carregando dados do sistema...' ]; for (const message of startupSteps.slice(0, 3)) { setSync({ online: false, status: 'syncing', message }); await wait(520); } const health = await fetchJson(apiUrl('health')); const checkedTables = health.migration?.tables?.length ?? 0; setSync({ online: false, status: 'syncing', message: checkedTables ? `Banco pronto: ${checkedTables} tabelas verificadas.` : startupSteps[3] }); await wait(520); setSync({ online: false, status: 'syncing', message: startupSteps[3] }); const payload = await fetchJson(apiUrl('continuous-data')); if (payload.user) setAuth(payload.user); const serverData = normalizeData({ ...payload.data, activeModule: data.activeModule, activeImprovementId: payload.data?.improvements?.[0]?.id ?? null }); setData(serverData); setSync({ online: true, status: 'online', message: 'MariaDB conectado e tabelas atualizadas' }); } catch (error) { setSync({ online: false, status: 'offline', message: error.message || 'Modo offline local' }); } } async function saveToServer(nextData, showMessage = true) { try { if (showMessage) setSync((current) => ({ ...current, status: 'syncing', message: 'Sincronizando...' })); await fetchJson(apiUrl('continuous-data'), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: nextData }) }); setSync({ online: true, status: showMessage ? 'online' : 'idle', message: showMessage ? 'Dados sincronizados' : '' }); } catch (error) { setSync({ online: false, status: 'offline', message: error.message || 'Salvo apenas offline' }); } } const filteredImprovements = useMemo(() => { const normalized = query.trim().toLowerCase(); if (!normalized) return data.improvements; return data.improvements.filter((item) => [ item.title, item.area, item.supervisor, item.problem, item.expectedGain, item.priority, statusLabel(item.status) ].join(' ').toLowerCase().includes(normalized)); }, [data.improvements, query]); const updateData = (updater) => setData((current) => normalizeData(typeof updater === 'function' ? updater(current) : updater)); const setActiveModule = (activeModule) => updateData((current) => ({ ...current, activeModule })); const selectImprovement = (id) => { updateData((current) => ({ ...current, activeImprovementId: id })); setImprovementModalOpen(true); }; const ensureToolData = (id, current) => ({ ...current.tools, [id]: activeTools(current, id) }); const addImprovement = (source = {}) => { const id = makeId('mc'); const improvement = { id, title: source.title ?? 'Nova oportunidade de melhoria', area: source.area ?? 'Operação', supervisor: source.supervisor ?? '', problem: source.problem ?? '', expectedGain: source.expectedGain ?? '', status: 'triagem', priority: source.urgency === 'Alta' ? 'Alta' : 'Média', impact: source.urgency === 'Alta' ? 5 : 3, effort: 3, dueDate: today(), createdAt: today() }; updateData((current) => ({ ...current, activeModule: 'pipeline', activeImprovementId: id, improvements: [improvement, ...current.improvements], pdca: { ...current.pdca, [id]: { plan: '', do: '', check: '', act: '' } }, tools: { ...current.tools, [id]: { pareto: defaultPareto, ishikawa: defaultIshikawa, fivew1h: defaultFiveW1H, sigma: defaultSigma } }, requests: source.id ? current.requests.map((request) => request.id === source.id ? { ...request, status: 'convertida' } : request) : current.requests })); setImprovementModalOpen(true); }; const updateImprovement = (id, patch) => updateData((current) => ({ ...current, improvements: current.improvements.map((item) => item.id === id ? { ...item, ...patch } : item), tools: ensureToolData(id, current) })); const deleteImprovement = (id) => updateData((current) => { const { [id]: removedPdca, ...pdca } = current.pdca; const { [id]: removedTools, ...tools } = current.tools; const improvements = current.improvements.filter((item) => item.id !== id); return { ...current, improvements, actions: current.actions.filter((action) => action.improvementId !== id), pdca, tools, activeImprovementId: improvements[0]?.id ?? null }; }); const addAction = () => { if (!activeImprovement) return; const action = { id: makeId('act'), improvementId: activeImprovement.id, what: 'Nova ação', why: '', who: '', when: today(), where: activeImprovement.area, how: '', howMuch: '', status: 'Pendente' }; updateData((current) => ({ ...current, activeModule: 'fivew2h', actions: [action, ...current.actions] })); }; const updateAction = (id, patch) => updateData((current) => ({ ...current, actions: current.actions.map((action) => action.id === id ? { ...action, ...patch } : action) })); const deleteAction = (id) => updateData((current) => ({ ...current, actions: current.actions.filter((action) => action.id !== id) })); const updatePdca = (phase, value) => { if (!activeImprovement) return; updateData((current) => ({ ...current, pdca: { ...current.pdca, [activeImprovement.id]: { ...(current.pdca[activeImprovement.id] ?? { plan: '', do: '', check: '', act: '' }), [phase]: value } } })); }; const updateTool = (tool, value) => { if (!activeImprovement) return; updateData((current) => ({ ...current, tools: { ...current.tools, [activeImprovement.id]: { ...activeTools(current, activeImprovement.id), [tool]: value } } })); }; const addMeeting = () => updateData((current) => ({ ...current, activeModule: 'meetings', meetings: [{ id: makeId('meet'), date: today(), supervisor: activeImprovement?.supervisor ?? '', area: activeImprovement?.area ?? 'Operação', topic: activeImprovement?.title ?? 'Alinhamento operacional', decisions: '', nextStep: '' }, ...current.meetings] })); const updateMeeting = (id, patch) => updateData((current) => ({ ...current, meetings: current.meetings.map((meeting) => meeting.id === id ? { ...meeting, ...patch } : meeting) })); const deleteMeeting = (id) => updateData((current) => ({ ...current, meetings: current.meetings.filter((meeting) => meeting.id !== id) })); const submitSupervisorRequest = async (request) => { const prepared = { ...request, id: makeId('req'), status: 'nova', createdAt: today() }; updateData((current) => ({ ...current, requests: [prepared, ...current.requests] })); if (sync.online) { try { await fetchJson(apiUrl('supervisor-request'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(prepared) }); setSync({ online: true, status: 'online', message: 'Solicitação enviada ao MariaDB' }); } catch (error) { setSync({ online: false, status: 'offline', message: 'Solicitação salva offline para sincronizar depois' }); } } }; const exportData = () => { const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `melhoria-continua-${today()}.json`; link.click(); URL.revokeObjectURL(url); }; const tools = activeImprovement ? activeTools(data, activeImprovement.id) : null; if (authLoading) { return ; } if (!auth) { return ; } return (
{data.activeModule === 'pipeline' && ( <> Analista de Melhoria Contínua

Sistema de melhoria contínua operacional

)}
setQuery(event.target.value)} placeholder="Buscar melhoria, área ou supervisor" />
{sync.status !== 'idle' && }
{data.activeModule === 'overview' && ( )} {data.activeModule === 'pipeline' && ( )} {['fivew1h', 'fivew2h'].includes(data.activeModule) && ( )} {data.activeModule === 'pdca' && ( )} {data.activeModule === 'pareto' && ( tools ? updateTool('pareto', value)} /> : )} {data.activeModule === 'ishikawa' && ( tools ? updateTool('ishikawa', value)} /> : )} {data.activeModule === 'sigma' && ( tools ? updateTool('sigma', value)} /> : )} {data.activeModule === 'meetings' && ( )} {data.activeModule === 'matrix' && ( )} {data.activeModule === 'library' && }
{activeImprovement && isImprovementModalOpen && ( setImprovementModalOpen(false)} onUpdate={(patch) => updateImprovement(activeImprovement.id, patch)} onDelete={() => { deleteImprovement(activeImprovement.id); setImprovementModalOpen(false); }} /> )}
); } function BootScreen({ text }) { return (
); } function LoginScreen({ onLogin }) { const [form, setForm] = useState({ email: '', password: '' }); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const update = (field, value) => setForm((current) => ({ ...current, [field]: value })); async function submit(event) { event.preventDefault(); setLoading(true); setError(''); try { await onLogin(form); } catch (exception) { setError(exception.message || 'Falha no login.'); } finally { setLoading(false); } } return (
Acesso restrito

Melhoria Contínua

Entre com o usuário da empresa para visualizar apenas os dados permitidos.

{error &&
{error}
}
); } function Sidebar({ activeModule, onModuleChange, sync, auth, onLogout }) { const modules = [ { id: 'overview', label: 'Visão geral', icon: LayoutDashboard }, { id: 'pipeline', label: 'Pipeline de melhorias', icon: Lightbulb }, { id: 'fivew2h', label: '5W2H / ações', icon: ListChecks }, { id: 'pdca', label: 'PDCA', icon: Target }, { id: 'pareto', label: 'Pareto', icon: BarChart3 }, { id: 'ishikawa', label: 'Ishikawa', icon: Fish }, { id: 'sigma', label: 'Six Sigma DMAIC', icon: Sigma }, { id: 'meetings', label: 'Reuniões', icon: CalendarDays }, { id: 'matrix', label: 'Impacto x esforço', icon: Flag }, { id: 'library', label: 'Biblioteca', icon: BookOpen } ]; const companySlug = auth?.company?.slug ?? 'meli'; return ( ); } function SyncBanner({ sync }) { return (
{sync.status === 'syncing' &&
); } function Overview({ improvements, actions, requests, onModuleChange, onSelect }) { const active = improvements.filter((item) => item.status !== 'padronizado').length; const done = improvements.filter((item) => item.status === 'padronizado').length; const lateActions = actions.filter((action) => action.status !== 'Concluída' && action.when < today()).length; const openRequests = requests.filter((request) => request.status === 'nova').length; return ( <>
0} />
onModuleChange('pipeline')}> {improvements.slice(0, 6).map((item) => ( ))} onModuleChange('supervisor')}> {requests.slice(0, 6).map((request) => (
{request.title} {request.area} · {request.supervisor || 'Supervisor não informado'} · Urgência {request.urgency}
))}
); } function Metric({ icon: Icon, label, value, detail, danger = false }) { return (
{label} {value} {detail}
); } function Panel({ title, label, action, onAction, children }) { return (
{label}

{title}

{action && }
{children}
); } function Modal({ title, label, onClose, children }) { return (
event.stopPropagation()}>
{label}

{title}

{children}
); } function SupervisorForm({ onSubmit, requests, onConvert }) { const [isOpen, setOpen] = useState(false); const [form, setForm] = useState({ title: '', area: '', supervisor: '', problem: '', expectedGain: '', urgency: 'Média' }); const update = (field, value) => setForm((current) => ({ ...current, [field]: value })); const submit = (event) => { event.preventDefault(); onSubmit(form); setForm({ title: '', area: '', supervisor: '', problem: '', expectedGain: '', urgency: 'Média' }); setOpen(false); }; return (
Fila do analista

Solicitações recebidas dos supervisores

{requests.map((request) => (
{request.title} {request.area} · {request.supervisor} · {request.urgency}

{request.problem}

))} {!requests.length && }
{isOpen && ( setOpen(false)}>