// Caririaçu Guincho — Relatórios + Configurações Screens
const { useState: useStateR, useEffect: useEffectR } = React;
// ── RELATÓRIOS ─────────────────────────────────────────────────────────────
const RELATORIOS = [
{ id: 'assistencias', icon: 'clipboard', color: '#3B82F6', title: 'Relatório de Assistências', desc: 'Histórico completo de atendimentos, por período, motorista ou seguradora.' },
{ id: 'financeiro', icon: 'dollar', color: '#10B981', title: 'Relatório Financeiro', desc: 'Entradas, saídas e saldo por período. Análise de rentabilidade.' },
{ id: 'motorista', icon: 'users', color: '#8B5CF6', title: 'Relatório por Motorista', desc: 'Performance individual: assistências, valores, tempo médio.' },
{ id: 'seguradora', icon: 'shield', color: '#F59E0B', title: 'Relatório por Seguradora', desc: 'Volume e faturamento por parceira. Comparativo mensal.' },
{ id: 'manutencoes', icon: 'wrench', color: '#EF4444', title: 'Relatório de Manutenções', desc: 'Manutenções da frota com custos e tipos por veículo.' },
];
const RelatoriosScreen = () => {
const [genModal, setGenModal] = useStateR(false);
const [selRel, setSelRel] = useStateR(null);
const [motoristas, setMotoristas] = useStateR([]);
const [seguradoras, setSeguradoras] = useStateR([]);
const [stats, setStats] = useStateR({ totalAss: 0, finalizadas: 0, faturamento: 0, ticket: 0, motoristasAtivos: 0, manutencoes: 0 });
const [form, setForm] = useStateR({ dataIni: new Date(Date.now() - 30 * 86400000).toISOString().slice(0,10), dataFim: new Date().toISOString().slice(0,10), motorista: '', seguradora: '', status: '' });
const set = (k,v) => setForm(f=>({...f,[k]:v}));
useEffectR(() => {
(async () => {
const [m, s, ass, manut] = await Promise.all([
db.list('motoristas', { order: 'nome', asc: true }),
db.list('seguradoras', { order: 'nome', asc: true }),
db.list('assistencias'),
db.list('manutencoes'),
]);
setMotoristas(m);
setSeguradoras(s);
const finalizadas = ass.filter(a => a.status === 'finalizada');
const faturamento = finalizadas.reduce((sum, a) => sum + Number(a.valor || 0), 0);
setStats({
totalAss: ass.length,
finalizadas: finalizadas.length,
faturamento,
ticket: finalizadas.length > 0 ? faturamento / finalizadas.length : 0,
motoristasAtivos: m.filter(x => x.status !== 'inativo').length,
manutencoes: manut.length,
});
})();
}, []);
const openGen = (rel) => { setSelRel(rel); setGenModal(true); };
const handleGen = async () => {
const dataEmissao = new Date().toLocaleDateString('pt-BR');
const numDoc = `REL-${selRel.id.toUpperCase()}-${Date.now().toString().slice(-6)}`;
const periodoLabel = `${new Date(form.dataIni).toLocaleDateString('pt-BR')} a ${new Date(form.dataFim).toLocaleDateString('pt-BR')}`;
let titulo = selRel.title;
let colunas = [];
let linhas = [];
let totaisHTML = '';
if (selRel.id === 'assistencias') {
let rows = await db.list('assistencias', { order: 'data' });
rows = rows.filter(r => {
const d = new Date(r.data).toISOString().slice(0,10);
return d >= form.dataIni && d <= form.dataFim;
});
if (form.motorista) rows = rows.filter(r => r.motorista_id === parseInt(form.motorista));
if (form.seguradora) rows = rows.filter(r => r.seguradora_id === parseInt(form.seguradora));
if (form.status) rows = rows.filter(r => r.status === form.status);
const total = rows.reduce((s, a) => s + Number(a.valor || 0), 0);
colunas = ['Nº','Data','Placa','Veículo','Serviço','Cliente','Motorista','Valor','Status'];
linhas = rows.map(a => [
`${a.numero || '#' + a.id} `,
new Date(a.data).toLocaleDateString('pt-BR'), a.placa || '', a.veiculo || '', a.servico, a.cliente, (a.motorista_nome || '').split(' ')[0],
`R$ ${Number(a.valor).toFixed(2)} `,
`${a.status} `
]);
totaisHTML = `
Total de registros ${rows.length}
FATURAMENTO TOTAL R$ ${total.toFixed(2)}
`;
} else if (selRel.id === 'financeiro') {
let rows = await db.list('transacoes', { order: 'data' });
rows = rows.filter(r => r.data >= form.dataIni && r.data <= form.dataFim);
const ent = rows.filter(t => t.tipo === 'entrada').reduce((s,t) => s + Number(t.valor || 0), 0);
const sai = rows.filter(t => t.tipo === 'saida').reduce((s,t) => s + Number(t.valor || 0), 0);
colunas = ['Data','Descrição','Categoria','Tipo','Valor'];
linhas = rows.map(t => [
new Date(t.data).toLocaleDateString('pt-BR'), t.descricao, t.categoria || '—',
`${t.tipo} `,
`${t.tipo === 'entrada' ? '+' : '−'} R$ ${Number(t.valor).toFixed(2)} `
]);
totaisHTML = `Total Entradas R$ ${ent.toFixed(2)}
Total Saídas R$ ${sai.toFixed(2)}
SALDO R$ ${(ent-sai).toFixed(2)}
`;
} else if (selRel.id === 'motorista') {
const rows = await db.list('motoristas', { order: 'nome', asc: true });
colunas = ['Motorista','Telefone','CNH','Categoria','Guincho','Assistências','Status'];
linhas = rows.map(m => [
m.nome, m.telefone || '—', m.cnh || '—', m.categoria ? `Cat. ${m.categoria}` : '—', m.guincho_code || '—',
`${m.assistencias || 0} `,
`${m.status} `
]);
} else if (selRel.id === 'seguradora') {
const rows = await db.list('seguradoras', { order: 'nome', asc: true });
const totalFat = rows.reduce((s, r) => s + Number(r.faturamento || 0), 0);
colunas = ['Seguradora','Contato','Telefone','Email','Assist./mês','Faturamento','Status'];
linhas = rows.map(s => [
s.nome, s.contato || '—', s.telefone || '—', s.email || '—',
`${s.assistencias_mes || 0} `,
`R$ ${Number(s.faturamento).toFixed(2)} `,
`${s.status} `
]);
totaisHTML = `FATURAMENTO TOTAL R$ ${totalFat.toFixed(2)}
`;
} else if (selRel.id === 'manutencoes') {
let rows = await db.list('manutencoes', { order: 'data' });
rows = rows.filter(r => r.data >= form.dataIni && r.data <= form.dataFim);
const total = rows.reduce((s, m) => s + Number(m.valor || 0), 0);
colunas = ['Data','Guincho','Tipo','Descrição','Oficina','Valor'];
linhas = rows.map(m => [
new Date(m.data).toLocaleDateString('pt-BR'), m.guincho_code || '—',
`${m.tipo} `,
m.descricao, m.oficina || '—',
`R$ ${Number(m.valor).toFixed(2)} `
]);
totaisHTML = `TOTAL EM MANUTENÇÕES R$ ${total.toFixed(2)}
`;
}
const linhasHTML = linhas.length === 0
? `Nenhum registro encontrado para os filtros selecionados `
: linhas.map(l => `${l.map(c => `${c} `).join('')} `).join('');
const html = `${titulo}
📥 Salvar como PDF / Imprimir
${colunas.map(c => `${c} `).join('')}
${linhasHTML}
${totaisHTML ? `
${totaisHTML}
` : ''}
`;
const win = window.open('', '_blank', 'width=1100,height=900');
if (!win) { showToast('Habilite popups para gerar o PDF', 'warning'); return; }
win.document.write(html); win.document.close();
showToast(`${titulo} gerado`, 'success');
setGenModal(false);
};
const fmtBRL = v => Number(v || 0).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', maximumFractionDigits: 0 });
return (
Relatórios
Exporte dados em PDF com layout profissional
{RELATORIOS.map(rel => (
openGen(rel)}
onMouseEnter={e=>{ e.currentTarget.style.boxShadow=`0 8px 24px ${rel.color}25`; e.currentTarget.style.transform='translateY(-2px)'; e.currentTarget.style.borderColor=rel.color+'50'; }}
onMouseLeave={e=>{ e.currentTarget.style.boxShadow='0 1px 3px rgba(0,0,0,0.07)'; e.currentTarget.style.transform=''; e.currentTarget.style.borderColor='#E2E8F0'; }}>
{ e.stopPropagation(); openGen(rel); }} style={{ alignSelf:'flex-start' }}>Gerar Relatório
))}
Resumo Geral
{[
{ label:'Total Assistências', val: stats.totalAss, color:'#3B82F6' },
{ label:'Finalizadas', val: stats.finalizadas, color:'#10B981' },
{ label:'Faturamento', val: fmtBRL(stats.faturamento), color:'#F59E0B' },
{ label:'Ticket Médio', val: fmtBRL(stats.ticket), color:'#8B5CF6' },
{ label:'Motoristas Ativos', val: stats.motoristasAtivos, color:'#64748B' },
{ label:'Manutenções', val: stats.manutencoes, color:'#EF4444' },
].map(s=>(
))}
setGenModal(false)} title={selRel ? `Gerar: ${selRel.title}` : ''} width={500}>
set('dataIni',e.target.value)}/>
set('dataFim',e.target.value)}/>
{(selRel?.id === 'motorista' || selRel?.id === 'assistencias') && (
set('motorista',e.target.value)}
options={[{value:'',label:'Todos os motoristas'},...motoristas.map(m=>({value:m.id,label:m.nome}))]}/>
)}
{(selRel?.id === 'seguradora' || selRel?.id === 'assistencias') && (
set('seguradora',e.target.value)}
options={[{value:'',label:'Todas as seguradoras'},...seguradoras.map(s=>({value:s.id,label:s.nome}))]}/>
)}
{selRel?.id === 'assistencias' && (
set('status',e.target.value)}
options={[{value:'',label:'Todos'},{value:'finalizada',label:'Finalizadas'},{value:'cancelada',label:'Canceladas'},{value:'aberta',label:'Abertas'}]}/>
)}
O PDF será aberto em uma nova aba com cabeçalho, tabela de dados e rodapé profissional.
setGenModal(false)}>Cancelar
Exportar PDF
);
};
// ── CONFIGURAÇÕES ──────────────────────────────────────────────────────────
const ConfigScreen = () => {
const [activeTab, setActiveTab] = useStateR('empresa');
const [isMobileCfg, setIsMobileCfg] = useStateR(window.innerWidth < 768);
const [navOpen, setNavOpen] = useStateR(false);
useEffectR(() => {
const handler = () => setIsMobileCfg(window.innerWidth < 768);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
const [empresa, setEmpresa] = useStateR({ nome: 'Caririaçu Guincho', cnpj: '', telefone: '', email: '', endereco: '' });
const [empresaId, setEmpresaId] = useStateR(1);
const [users, setUsers] = useStateR([]);
const [categorias, setCategorias] = useStateR([]);
const [alertConfig, setAlertConfig] = useStateR({ id: 1, horas_aberta: 4, email_enabled: true, vistoria_pendente: true, manutencao_alert: false });
const [editingCat, setEditingCat] = useStateR(null);
const [editingCatVal, setEditingCatVal] = useStateR('');
const [novaCat, setNovaCat] = useStateR('');
const [showNovaCat, setShowNovaCat] = useStateR(false);
const [userModal, setUserModal] = useStateR(false);
const [editingUser, setEditingUser] = useStateR(null);
const [userForm, setUserForm] = useStateR({ nome:'', email:'', cargo:'Operador', status:'ativo' });
const setEmp = (k, v) => setEmpresa(e => ({ ...e, [k]: v }));
const setUF = (k, v) => setUserForm(f => ({ ...f, [k]: v }));
useEffectR(() => {
(async () => {
const [emp] = await db.list('empresa');
if (emp) { setEmpresa(emp); setEmpresaId(emp.id); }
setUsers(await db.list('usuarios', { order: 'nome', asc: true }));
const cats = await db.list('categorias_financeiras', { order: 'nome', asc: true });
setCategorias(cats.map(c => c.nome));
const [conf] = await db.list('config_alertas');
if (conf) setAlertConfig(conf);
})();
}, []);
const saveEmpresa = async () => {
const updated = await db.update('empresa', empresaId, {
nome: empresa.nome, cnpj: empresa.cnpj, telefone: empresa.telefone,
email: empresa.email, endereco: empresa.endereco, updated_at: new Date().toISOString()
});
if (updated) showToast('Dados da empresa salvos!', 'success');
};
const saveAlertas = async () => {
const updated = await db.update('config_alertas', 1, {
horas_aberta: alertConfig.horas_aberta,
email_enabled: alertConfig.email_enabled,
vistoria_pendente: alertConfig.vistoria_pendente,
manutencao_alert: alertConfig.manutencao_alert,
updated_at: new Date().toISOString()
});
if (updated) showToast('Alertas salvos!', 'success');
};
const addCategoria = async (nome) => {
if (!nome.trim()) return;
const created = await db.insert('categorias_financeiras', { nome: nome.trim(), tipo: 'ambos' });
if (created) { setCategorias(c => [...c, created.nome]); showToast('Categoria adicionada!', 'success'); }
};
const removeCategoria = async (nome) => {
const all = await db.list('categorias_financeiras', { eq: { nome } });
if (all[0]) {
const ok = await db.remove('categorias_financeiras', all[0].id);
if (ok) { setCategorias(c => c.filter(x => x !== nome)); showToast(`"${nome}" removida`, 'warning'); }
}
};
const renameCategoria = async (oldName, newName) => {
if (!newName.trim()) return;
const all = await db.list('categorias_financeiras', { eq: { nome: oldName } });
if (all[0]) {
const updated = await db.update('categorias_financeiras', all[0].id, { nome: newName.trim() });
if (updated) { setCategorias(c => c.map(x => x === oldName ? newName.trim() : x)); showToast('Categoria atualizada!', 'success'); }
}
};
const TABS = [
{ id:'empresa', label:'Empresa', icon:'building' },
{ id:'usuarios', label:'Usuários', icon:'users' },
{ id:'categorias', label:'Categorias', icon:'clipboard' },
{ id:'alertas', label:'Alertas', icon:'bell' },
{ id:'combustiveis', label:'Combustíveis', icon:'zap' },
{ id:'tipos-despesa', label:'Tipos de despesa', icon:'dollar' },
{ id:'postos', label:'Postos', icon:'map' },
{ id:'checklists', label:'Templates checklist', icon:'clipboard' },
{ id:'backup', label:'Backup', icon:'download' },
];
// Backup
const [backupRunning, setBackupRunning] = useStateR(false);
const [lastBackup, setLastBackup] = useStateR(localStorage.getItem('caririacu_last_backup') || null);
const fazerBackup = async () => {
setBackupRunning(true);
try {
showToast('Gerando backup completo...', 'info');
const tabelas = ['empresa','usuarios','motoristas','frota','seguradoras','assistencias','manutencoes','transacoes','categorias_financeiras','config_alertas','vistorias'];
const dados = {};
for (const t of tabelas) {
dados[t] = await db.list(t);
}
const backup = {
empresa: 'Caririaçu Guincho',
gerado_em: new Date().toISOString(),
gerado_em_br: new Date().toLocaleString('pt-BR'),
versao: '1.0',
total_registros: Object.values(dados).reduce((s, arr) => s + arr.length, 0),
dados,
};
const blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const dataStr = new Date().toISOString().slice(0,10);
a.href = url;
a.download = `backup-caririacu-${dataStr}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
const agora = new Date().toISOString();
localStorage.setItem('caririacu_last_backup', agora);
setLastBackup(agora);
showToast(`Backup gerado! ${backup.total_registros} registros salvos`, 'success');
} catch (e) {
showToast('Erro ao gerar backup', 'error');
console.error(e);
}
setBackupRunning(false);
};
const diasDesdeUltimoBackup = lastBackup
? Math.floor((new Date() - new Date(lastBackup)) / (1000 * 60 * 60 * 24))
: null;
const openNewUser = () => { setEditingUser(null); setUserForm({ nome:'', email:'', cargo:'Operador', status:'ativo' }); setUserModal(true); };
const openEditUser = (u) => { setEditingUser(u); setUserForm({ nome:u.nome, email:u.email, cargo:u.cargo, status:u.status }); setUserModal(true); };
const saveUser = async () => {
if (!userForm.nome || !userForm.email) { showToast('Preencha nome e e-mail', 'warning'); return; }
if (editingUser) {
const updated = await db.update('usuarios', editingUser.id, { nome: userForm.nome, cargo: userForm.cargo, status: userForm.status });
if (updated) {
setUsers(us => us.map(u => u.id === editingUser.id ? { ...u, ...userForm } : u));
showToast(`${userForm.nome} atualizado`, 'success');
}
} else {
showToast('Para convidar, peça ao novo usuário para se cadastrar via tela de login (em breve: convite por e-mail)', 'info');
}
setUserModal(false);
};
const toggleUserStatus = async (u) => {
const novoStatus = u.status === 'inativo' ? 'ativo' : 'inativo';
const updated = await db.update('usuarios', u.id, { status: novoStatus });
if (updated) { setUsers(us => us.map(x => x.id === u.id ? { ...x, status: novoStatus } : x)); showToast('Status atualizado', 'success'); }
};
const tabAtual = TABS.find(t => t.id === activeTab) || TABS[0];
const tabButtonStyle = (active) => ({
display:'flex', alignItems:'center', gap:10, padding:'10px 12px',
borderRadius:8, border:'none', cursor:'pointer', width:'100%',
background: active ? 'rgba(245,158,11,0.1)' : 'transparent',
color: active ? '#B45309' : '#64748B',
fontWeight: active ? 600 : 400, fontSize:14,
fontFamily:'Inter, sans-serif', transition:'all 0.15s', textAlign:'left'
});
return (
{isMobileCfg ? (
setNavOpen(o => !o)} style={{
width:'100%', padding:'12px 14px', borderRadius:10,
border:'1px solid #E2E8F0', background:'#fff',
display:'flex', alignItems:'center', justifyContent:'space-between',
fontSize:14, fontWeight:600, color:'#0F172A', cursor:'pointer',
fontFamily:'Inter, sans-serif'
}}>
{tabAtual.label}
{navOpen ? '▲' : '▼'}
{navOpen && (
<>
setNavOpen(false)} style={{
position:'fixed', inset:0, zIndex:49, background:'transparent'
}}/>
{TABS.map(t => (
{ setActiveTab(t.id); setNavOpen(false); }}
style={tabButtonStyle(activeTab === t.id)}>
{t.label}
))}
>
)}
) : (
{TABS.map(t=>(
setActiveTab(t.id)} style={tabButtonStyle(activeTab===t.id)}>
{t.label}
))}
)}
{activeTab === 'empresa' && (
Dados da Empresa
)}
{activeTab === 'usuarios' && (
Usuários e Permissões
Convidar
{users.length === 0 ? (
Nenhum usuário cadastrado ainda.
) : (
{['Usuário','Email','Cargo','Status','Ações'].map(h=>{h} )}
{users.map((u,i)=>(
w[0]).join('').slice(0,2).toUpperCase()} size={30}/>{u.nome}
{u.email}
{u.cargo}
openEditUser(u)} style={{ background:'none',border:'none',cursor:'pointer',color:'#64748B',padding:4,borderRadius:6 }} onMouseEnter={e=>e.currentTarget.style.background='#F1F5F9'} onMouseLeave={e=>e.currentTarget.style.background='none'}>
toggleUserStatus(u)} style={{ background:'none',border:'none',cursor:'pointer',color:u.status==='inativo'?'#10B981':'#EF4444',padding:4,borderRadius:6 }} onMouseEnter={e=>e.currentTarget.style.background='#F8FAFC'} onMouseLeave={e=>e.currentTarget.style.background='none'}>
))}
)}
)}
{activeTab === 'alertas' && (
Configuração de Alertas
Alerta de Assistência Aberta
Notificar quando uma assistência ficar aberta sem atualização por:
setAlertConfig(c => ({ ...c, horas_aberta: +e.target.value }))} style={{ flex:1, accentColor:'#F59E0B' }}/>
{alertConfig.horas_aberta}h
1 hora 12 horas
{[
{ key: 'email_enabled', label:'Notificação por email', desc:'Enviar email para administradores quando alerta disparar' },
{ key: 'vistoria_pendente', label:'Alerta de vistoria pendente', desc:'Avisar quando assistência não tem vistoria após 30 min' },
{ key: 'manutencao_alert', label:'Alerta de manutenção', desc:'Notificar quando guincho está em manutenção há mais de 48h' },
].map(a=>(
setAlertConfig(c => ({ ...c, [a.key]: !c[a.key] }))} style={{
width:44, height:24, borderRadius:12, background: alertConfig[a.key] ? '#F59E0B' : '#E2E8F0',
cursor:'pointer', position:'relative', transition:'background 0.2s', flexShrink:0
}}>
))}
Salvar Configurações
)}
{activeTab === 'backup' && (
Backup dos Dados
Faça download de todos os dados do sistema em um único arquivo JSON.
{/* Status do último backup */}
7 ? '#FEF2F2' : diasDesdeUltimoBackup > 1 ? '#FFFBEB' : '#ECFDF5')
: '#FEF2F2',
border: lastBackup
? `1px solid ${diasDesdeUltimoBackup > 7 ? '#FECACA' : diasDesdeUltimoBackup > 1 ? '#FED7AA' : '#A7F3D0'}`
: '1px solid #FECACA',
marginBottom:20, display:'flex', alignItems:'center', gap:14
}}>
7 ? '#FEE2E2' : diasDesdeUltimoBackup > 1 ? '#FFF3C4' : '#D1FAE5')
: '#FEE2E2',
display:'flex', alignItems:'center', justifyContent:'center'
}}>
7 ? 'alert' : 'check') : 'alert'} size={22}
color={lastBackup ? (diasDesdeUltimoBackup > 7 ? '#DC2626' : diasDesdeUltimoBackup > 1 ? '#D97706' : '#059669') : '#DC2626'} />
{lastBackup ? (
<>
{diasDesdeUltimoBackup === 0 ? 'Backup feito hoje ✓' : diasDesdeUltimoBackup === 1 ? 'Backup feito ontem' : `Último backup há ${diasDesdeUltimoBackup} dias`}
{new Date(lastBackup).toLocaleString('pt-BR')}
>
) : (
<>
Nenhum backup feito ainda
Recomendado fazer backup pelo menos uma vez por semana
>
)}
{backupRunning ? 'Gerando...' : 'Fazer Backup Agora'}
{/* Frequência recomendada */}
Recomendado para empresas com muito movimento. Faça antes do final do expediente todo dia.
Mínimo recomendado. Faça aos domingos ou segundas para garantir todos os dados da semana.
{/* O que está incluído */}
📦 O backup inclui:
{[
'Todas as Assistências e OS',
'Frota completa de guinchos',
'Cadastro de motoristas',
'Cadastro de seguradoras',
'Histórico de manutenções',
'Transações financeiras',
'Vistorias com checklist',
'Configurações da empresa',
'Categorias financeiras',
'Usuários do sistema',
].map(item => (
{item}
))}
Backup automático na nuvem: além desse backup manual, o Supabase já faz backup automático diário do banco de dados. Esse backup em JSON é uma cópia adicional pra você guardar localmente.
)}
{activeTab === 'categorias' && (
Categorias Financeiras
{categorias.length === 0 && !showNovaCat && (
Nenhuma categoria cadastrada.
)}
{categorias.map((c,i)=>(
{editingCat === i ? (
<>
setEditingCatVal(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') { renameCategoria(c, editingCatVal); setEditingCat(null); } if (e.key === 'Escape') setEditingCat(null); }}
style={{ flex:1, padding:'4px 8px', borderRadius:6, border:'1px solid #3B82F6', fontSize:14, outline:'none', marginRight:8 }} />
{ renameCategoria(c, editingCatVal); setEditingCat(null); }}
style={{ background:'none',border:'none',cursor:'pointer',color:'#10B981',padding:4,borderRadius:5 }}>
setEditingCat(null)}
style={{ background:'none',border:'none',cursor:'pointer',color:'#94A3B8',padding:4,borderRadius:5 }}>
>
) : (
<>
{c}
{ setEditingCat(i); setEditingCatVal(c); }}
style={{ background:'none',border:'none',cursor:'pointer',color:'#64748B',padding:4,borderRadius:5 }}>
removeCategoria(c)}
style={{ background:'none',border:'none',cursor:'pointer',color:'#EF4444',padding:4,borderRadius:5 }}>
>
)}
))}
{showNovaCat ? (
setNovaCat(e.target.value)} placeholder="Nome da categoria..."
onKeyDown={e => { if (e.key === 'Enter' && novaCat.trim()) { addCategoria(novaCat); setNovaCat(''); setShowNovaCat(false); } if (e.key === 'Escape') { setShowNovaCat(false); setNovaCat(''); } }}
style={{ flex:1, padding:'4px 8px', borderRadius:6, border:'1px solid #F59E0B', fontSize:14, outline:'none' }} />
{ if (novaCat.trim()) { addCategoria(novaCat); setNovaCat(''); setShowNovaCat(false); } }}
style={{ background:'#F59E0B',border:'none',cursor:'pointer',color:'#0F172A',padding:'6px 10px',borderRadius:6, fontWeight:700, fontSize:13 }}>Adicionar
{ setShowNovaCat(false); setNovaCat(''); }}
style={{ background:'none',border:'none',cursor:'pointer',color:'#94A3B8',padding:4,borderRadius:5 }}>
) : (
setShowNovaCat(true)} style={{ marginTop:12 }}>Nova Categoria
)}
)}
{activeTab === 'combustiveis' &&
}
{activeTab === 'tipos-despesa' &&
}
{activeTab === 'postos' &&
}
{activeTab === 'checklists' &&
}
setUserModal(false)} title={editingUser ? `Editar ${editingUser.nome}` : 'Convidar Usuário'} width={480}>
setUF('nome', e.target.value)}/>
setUF('email', e.target.value)} disabled={!!editingUser}/>
setUF('cargo', e.target.value)}
options={[{value:'Administrador',label:'Administrador'},{value:'Operador',label:'Operador'},{value:'Motorista',label:'Motorista'}]}/>
setUF('status', e.target.value)}
options={[{value:'ativo',label:'Ativo'},{value:'inativo',label:'Inativo'}]}/>
{!editingUser && (
Por enquanto, novos usuários precisam se cadastrar diretamente no Supabase Authentication. Aqui você pode editar permissões dos já cadastrados.
)}
setUserModal(false)}>Cancelar
{editingUser ? 'Salvar' : 'OK'}
);
};
Object.assign(window, { RelatoriosScreen, ConfigScreen });