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
{request.problem}
{description}