const { useState, useMemo, useRef, createContext, useContext, useEffect, useCallback } = React;

// ═══ ICONS ═══
const I = {
  dash: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>,
  users: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>,
  client: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>,
  work: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg>,
  money: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>,
  flow: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>,
  svc: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>,
  search: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>,
  plus: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>,
  chev: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>,
  close: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>,
  check: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>,
  tooth: <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M12 2C9.5 2 7 3.5 6 6c-1.5 3.5-.5 6 .5 9 .7 2 1 4 1.5 5.5.3.8 1 1.5 2 1.5s1.5-1 2-2.5c.3-1 .3-2 0-3 .3 1 .3 2 0 3 .5 1.5 1 2.5 2 2.5s1.7-.7 2-1.5c.5-1.5.8-3.5 1.5-5.5 1-3 2-5.5.5-9-1-2.5-3.5-4-6-4z"/></svg>,
  bell: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>,
  eye: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>,
  eyeOff: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>,
  edit: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>,
  cam: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>,
  cube: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>,
  scan: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><line x1="7" y1="12" x2="17" y2="12"/></svg>,
  logout: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>,
  back: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="15 18 9 12 15 6"/></svg>,
  dl: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>,
  up: <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>,
  down: <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>,
  bill: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>,
  repeat: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>,
  assign: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/></svg>,
  pay: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>,
  overview: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M21.21 15.89A10 10 0 1 1 8 2.83"/><path d="M22 12A10 10 0 0 0 12 2v10z"/></svg>,
  mywork: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>,
  file: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>,
  img: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>,
  trash: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>,
  cashflow: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/><line x1="2" y1="20" x2="22" y2="20"/></svg>,
  lock: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>,
  settings: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>,
  menu: <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>,
  mail: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>,
  tag: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>,
  // STL mesh logo
  stl: <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round"><polygon points="12,2 4,8 12,14" fill="rgba(14,165,233,0.15)"/><polygon points="12,2 20,8 12,14" fill="rgba(14,165,233,0.25)"/><polygon points="4,8 12,14 4,20" fill="rgba(14,165,233,0.1)"/><polygon points="20,8 12,14 20,20" fill="rgba(14,165,233,0.2)"/><polygon points="12,14 4,20 12,22" fill="rgba(14,165,233,0.15)"/><polygon points="12,14 20,20 12,22" fill="rgba(14,165,233,0.3)"/><line x1="12" y1="2" x2="4" y2="8"/><line x1="12" y1="2" x2="20" y2="8"/><line x1="4" y1="8" x2="12" y2="14"/><line x1="20" y1="8" x2="12" y2="14"/><line x1="4" y1="8" x2="4" y2="20"/><line x1="20" y1="8" x2="20" y2="20"/><line x1="4" y1="20" x2="12" y2="22"/><line x1="20" y1="20" x2="12" y2="22"/><line x1="12" y1="14" x2="4" y2="20"/><line x1="12" y1="14" x2="20" y2="20"/></svg>,
};

// ═══ CONSTANTS ═══
const STATUS_FLOW=[{id:"recebido",label:"Recebido",color:"#64748B"},{id:"planejamento",label:"Em Planejamento",color:"#0EA5E9"},{id:"producao",label:"Em Produção",color:"#F59E0B"},{id:"revisao",label:"Revisão",color:"#8B5CF6"},{id:"finalizado",label:"Finalizado",color:"#10B981"},{id:"entregue",label:"Entregue",color:"#059669"}];
const STM=Object.fromEntries(STATUS_FLOW.map(s=>[s.id,s]));
const ROLES=[{id:"admin",label:"Administrador",color:"#EF4444"},{id:"tecnico",label:"Técnico",color:"#0EA5E9"},{id:"financeiro",label:"Financeiro",color:"#10B981"},{id:"atendimento",label:"Atendimento",color:"#F59E0B"}];
const RM=Object.fromEntries(ROLES.map(r=>[r.id,r]));
const BILL_CATS=[{id:"material",label:"Material",color:"#0EA5E9"},{id:"equipamento",label:"Equipamento",color:"#8B5CF6"},{id:"software",label:"Software/Licença",color:"#EC4899"},{id:"aluguel",label:"Aluguel",color:"#F59E0B"},{id:"servico",label:"Serviço Terceirizado",color:"#10B981"},{id:"imposto",label:"Imposto/Taxa",color:"#EF4444"},{id:"outros",label:"Outros",color:"#64748B"}];
const BCM=Object.fromEntries(BILL_CATS.map(c=>[c.id,c]));
const PAY_METHODS=["PIX","Cartão Crédito","Cartão Débito","Dinheiro","Transferência Bancária","Boleto","Cheque"];
const SCAN_SLOTS=[{id:"sup",label:"Arcada Superior"},{id:"inf",label:"Arcada Inferior"},{id:"ext_sup",label:"Arcada Extra Superior"},{id:"ext_inf",label:"Arcada Extra Inferior"}];

// ═══ CSS ═══
const css=`
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Inter:wght@400;500;600&family=Space+Mono:wght@400;700&display=swap');
:root{--bg0:#0f111a;--bg1:rgba(21, 25, 38, 0.65);--bg2:rgba(29, 36, 51, 0.6);--bg2h:rgba(37, 46, 64, 0.8);--bg3:rgba(18, 22, 33, 0.8);--bd:rgba(255, 255, 255, 0.08);--bdf:#00E5FF;--t1:#FFFFFF;--t2:#A0AEC0;--t3:#718096;--ac:#0EA5E9;--acg:rgba(14, 165, 233, 0.15);--ok:#10B981;--wn:#F59E0B;--err:#FF3B5C;--pu:#8B5CF6;--pk:#EC4899;--r:16px;--rs:10px;--rx:8px;--sh:0 10px 30px rgba(0,0,0,0.4);--f:'Inter',sans-serif;--fh:'Outfit',sans-serif;--m:'Space Mono',monospace}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:var(--f);background:var(--bg0);color:var(--t1);-webkit-font-smoothing:antialiased; background: radial-gradient(circle at 50% -10%, #172136 0%, var(--bg0) 60%); min-height: 100vh;}
.app{display:flex;height:100vh;overflow:hidden; backdrop-filter: blur(8px);}
.sb{width:260px;min-width:260px;background:var(--bg1);border-right:1px solid var(--bd);display:flex;flex-direction:column;overflow-y:auto; backdrop-filter: blur(12px); transition: transform 0.3s cubic-bezier(0.4,0,0.2,1);}
.sb-brand{padding:24px 20px;border-bottom:1px solid rgba(255,255,255,0.04);display:flex;align-items:center;gap:12px; margin-bottom: 8px;}
.sb-logo{width:40px;height:40px;background:linear-gradient(135deg,var(--ac),#0284C7);border-radius:12px;display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0;box-shadow: 0 4px 15px var(--acg);}
.sb-brand h1{font-family:var(--fh);font-size:16px;font-weight:700;letter-spacing:-.01em;line-height:1.2;color:var(--t1);}
.sb-brand .sub{font-size:10px;color:rgba(255,255,255,0.5);font-weight:600;letter-spacing:.08em;text-transform:uppercase}
.sb-sec{padding:8px 16px}
.sb-lbl{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:var(--t3);padding:0 8px;margin-bottom:8px}
.si{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:var(--rs);color:var(--t2);cursor:pointer;transition:all .2s ease;font-size:13px;font-weight:500;position:relative;margin-bottom:2px;}
.si:hover{background:rgba(255,255,255,0.04);color:var(--t1)}
.si.on{background:var(--acg);color:var(--ac)}
.si.on::before{content:'';position:absolute;left:-16px;top:50%;transform:translateY(-50%);width:4px;height:24px;background:var(--ac);border-radius:0 4px 4px 0;box-shadow:0 0 10px var(--acg);}
.sb-bg{margin-left:auto;background:var(--ac);color:var(--bg0);font-size:10px;font-weight:700;padding:3px 8px;border-radius:12px;min-width:20px;text-align:center;box-shadow:0 2px 8px var(--acg);}
.sb-bg.w{background:var(--err);color:#fff;}
.ssub{padding:6px 12px 6px 44px;font-size:12px;font-weight:500;color:var(--t3);cursor:pointer;border-radius:var(--rx);transition:all .2s ease;margin:2px 0}
.ssub:hover{background:rgba(255,255,255,0.03);color:var(--t2)}
.ssub.on{color:var(--ac);background:transparent; font-weight: 600;}
.sb-ft{margin-top:auto;padding:16px;border-top:1px solid rgba(255,255,255,0.04)}
.sb-u{display:flex;align-items:center;gap:10px;padding:8px;border-radius:var(--rs);cursor:pointer;transition:all .2s ease}
.sb-u:hover{background:rgba(255,255,255,0.04)}
.av{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ac),var(--pu));display:flex;align-items:center;justify-content:center;font-family:var(--fh);font-size:13px;font-weight:700;color:#fff;flex-shrink:0;box-shadow:0 4px 10px rgba(139,92,246,0.2);}
.mn{flex:1;display:flex;flex-direction:column;overflow:hidden}
.top{height:68px;min-height:68px;background:var(--bg1);border-bottom:1px solid rgba(255,255,255,0.04);display:flex;align-items:center;justify-content:space-between;padding:0 30px;backdrop-filter:blur(8px);z-index: 10;}
.top-t{font-family:var(--fh);font-size:18px;font-weight:600;}
.top-a{display:flex;align-items:center;gap:12px}
.hamburger{display:none;width:38px;height:38px;border-radius:var(--rs);border:1px solid rgba(255,255,255,0.08);background:rgba(0,0,0,0.15);color:var(--t2);align-items:center;justify-content:center;cursor:pointer;transition:all .2s ease;}
.hamburger:hover{background:rgba(255,255,255,0.08);color:var(--t1);}
.sb-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:98;}
.ibtn{width:38px;height:38px;border-radius:var(--rs);border:1px solid rgba(255,255,255,0.08);background:rgba(0,0,0,0.15);color:var(--t2);display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s ease;position:relative}
.ibtn:hover{background:rgba(255,255,255,0.08);color:var(--t1);transform:translateY(-1px);}
.pg{flex:1;overflow-y:auto;padding:24px 30px}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:24px; animation: fadeUp 0.4s ease-out forwards;}
.st{background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r);padding:20px;display:flex;flex-direction:column;gap:12px;transition:all .3s ease;backdrop-filter:blur(8px);box-shadow: 0 4px 20px rgba(0,0,0,0.15);}
.st:hover{border-color:var(--ac);transform:translateY(-4px);box-shadow: 0 8px 30px var(--acg);}
.st .sh{display:flex;align-items:center;justify-content:space-between}
.st .ico{width:42px;height:42px;border-radius:12px;display:flex;align-items:center;justify-content:center;box-shadow: 0 4px 10px rgba(0,0,0,0.1);}
.st .sv{font-size:28px;font-weight:700;font-family:var(--m);letter-spacing:-.04em;line-height:1;margin-top:4px}
.st .sl{font-size:12px;font-weight:500;color:var(--t2);text-transform:uppercase;letter-spacing:0.05em;}
.cd{background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r);overflow:hidden;backdrop-filter:blur(10px);box-shadow: 0 8px 32px rgba(0,0,0,0.2); animation: fadeUp 0.5s ease-out forwards;}
.cd-h{padding:18px 24px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid rgba(255,255,255,0.04);gap:12px;flex-wrap:wrap;background:rgba(0,0,0,0.1);}
.cd-h h3{font-family:var(--fh);font-size:15px;font-weight:600;white-space:nowrap}
.tb-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
.tb{width:100%;border-collapse:collapse;min-width:600px}
.tb th{text-align:left;padding:12px 20px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--t3);border-bottom:1px solid rgba(255,255,255,0.06);background:rgba(0,0,0,0.2);cursor:pointer;user-select:none;white-space:nowrap;transition:0.2s;}
.tb th:hover{color:var(--t2);background:rgba(255,255,255,0.02);}.tb th.so{color:var(--ac)}
.tb td{padding:14px 20px;font-size:13px;border-bottom:1px solid rgba(255,255,255,0.04);color:var(--t1);transition:0.2s; background: rgba(0,0,0,0.05);}
.tb tr:last-child td{border-bottom:none}
.tb tr:hover td{background:rgba(255,255,255,0.02);}
.badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:20px;font-size:11px;font-weight:600;white-space:nowrap;}
.badge .dt{width:6px;height:6px;border-radius:50%;box-shadow:0 0 6px currentColor;}
.chip{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:6px;font-size:11px;font-weight:600;}
.btn{display:inline-flex;align-items:center;gap:8px;padding:8px 16px;border-radius:var(--rs);border:none;font-family:var(--f);font-size:13px;font-weight:600;cursor:pointer;transition:all .3s cubic-bezier(0.4, 0, 0.2, 1);white-space:nowrap}
.btn:disabled{opacity:.5;cursor:not-allowed;transform:none !important;}
.bp{background:linear-gradient(135deg, var(--ac), #0284C7);color:#fff;box-shadow:0 4px 15px var(--acg);text-shadow: 0 1px 2px rgba(0,0,0,0.2);}.bp:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 6px 20px rgba(14,165,233,0.3);}
.bs{background:rgba(255,255,255,0.05);color:var(--t1);border:1px solid rgba(255,255,255,0.1);backdrop-filter:blur(4px);}.bs:hover:not(:disabled){background:rgba(255,255,255,0.1);border-color:rgba(255,255,255,0.2);transform:translateY(-1px);}
.bg{background:transparent;color:var(--t2)}.bg:hover:not(:disabled){background:rgba(255,255,255,0.05);color:var(--t1);transform:translateY(-1px);}
.bsm{padding:6px 12px;font-size:12px}
.bok{background:rgba(16,185,129,0.1);color:var(--ok);border:1px solid rgba(16,185,129,0.2);}.bok:hover:not(:disabled){background:rgba(16,185,129,0.15);border-color:var(--ok);box-shadow:0 0 10px rgba(16,185,129,0.2);transform:translateY(-1px);}
.bwn{background:rgba(245,158,11,0.1);color:var(--wn);border:1px solid rgba(245,158,11,0.2)}
.berr{background:rgba(239,68,68,0.1);color:var(--err);border:1px solid rgba(239,68,68,0.2);}.berr:hover:not(:disabled){background:rgba(239,68,68,0.15);transform:translateY(-1px);}
.fg{margin-bottom:16px}
.fl{display:block;font-size:11px;font-weight:500;color:var(--t2);margin-bottom:6px;letter-spacing:0.02em;}
.fi,.fs,.fta{width:100%;padding:10px 14px;background:rgba(0,0,0,0.2);border:1px solid rgba(255,255,255,0.08);border-radius:var(--rs);color:var(--t1);font-family:var(--f);font-size:13px;outline:none;transition:all .3s ease;box-shadow:inset 0 1px 3px rgba(0,0,0,0.1);}
.fi:focus,.fs:focus,.fta:focus{border-color:var(--ac);background:rgba(0,0,0,0.3);box-shadow:0 0 0 3px var(--acg);}
.fs{appearance:none;cursor:pointer}
.fs option{background: var(--bg1); color: var(--t1);}
.fta{resize:vertical;min-height:80px;line-height:1.5;}
.fr{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.fr3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}
.mo{position:fixed;inset:0;background:rgba(5, 7, 12, 0.7);backdrop-filter:blur(8px);z-index:999;display:flex;align-items:center;justify-content:center;animation:fi .2s ease-out}
.ml{background:var(--bg1);border:1px solid rgba(255,255,255,0.1);border-radius:20px;width:560px;max-width:92vw;max-height:88vh;overflow-y:auto;box-shadow:0 24px 80px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.05);animation:su .3s cubic-bezier(0.175, 0.885, 0.32, 1.275);backdrop-filter:blur(20px);}
.ml.lg{width:760px}
.ml-h{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid rgba(255,255,255,0.06);background:rgba(0,0,0,0.15);}
.ml-h h3{font-family:var(--fh);font-size:17px;font-weight:600;letter-spacing:-0.01em;}
.ml-b{padding:24px}
.ml-f{padding:16px 24px;border-top:1px solid rgba(255,255,255,0.06);display:flex;justify-content:flex-end;gap:12px;background:rgba(0,0,0,0.15);}
@keyframes fi{from{opacity:0}to{opacity:1}}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes su{from{opacity:0;transform:translateY(30px) scale(0.95)}to{opacity:1;transform:translateY(0) scale(1)}}
@keyframes fadeUp{from{opacity:0;transform:translateY(15px)}to{opacity:1;transform:translateY(0)}}
.pipe{display:flex;gap:16px;overflow-x:auto;padding-bottom:12px;}
.pcol{flex:0 0 250px;min-width:250px}
.pcol-h{display:flex;align-items:center;gap:8px;padding:12px 14px;margin-bottom:12px;border-radius:var(--rs);background:rgba(0,0,0,0.2);border:1px solid rgba(255,255,255,0.06)}
.pcol-h .d{width:10px;height:10px;border-radius:50%;box-shadow:0 0 8px currentColor;}
.pcol-h .t{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--t1)}
.pcol-h .c{margin-left:auto;font-family:var(--m);font-size:11px;font-weight:700;color:var(--ac);background:var(--acg);padding:3px 8px;border-radius:12px}
.pcard{background:var(--bg3);border:1px solid rgba(255,255,255,0.06);border-radius:var(--r);padding:14px;margin-bottom:10px;cursor:pointer;transition:all .3s ease;box-shadow:0 4px 12px rgba(0,0,0,0.15);}
.pcard:hover{border-color:var(--ac);transform:translateY(-3px);box-shadow:0 8px 20px rgba(0,0,0,0.25), 0 0 0 1px var(--acg);}
.pcard .pid{font-family:var(--m);font-size:11px;color:var(--t3);margin-bottom:4px}
.pcard .pcl{font-weight:600;color:var(--t1);margin-bottom:4px;font-size:13px;}
.pcard .psvc{font-size:11px;color:var(--t2);margin-bottom:8px}
.pcard .pfoo{display:flex;justify-content:space-between;align-items:center;padding-top:8px;border-top:1px dashed rgba(255,255,255,0.08);}
.pcard .pval{font-family:var(--m);font-size:13px;font-weight:700;color:var(--ok)}
.pcard .pdt{font-size:10px;color:var(--t3);font-weight:500;}
.pcard .pa{font-size:10px;color:var(--pu);margin-top:6px;background:rgba(139,92,246,0.15);display:inline-block;padding:2px 6px;border-radius:4px;font-weight:600;}
.sflow{display:flex;align-items:center;margin:24px 0}
.fstep{flex:1;text-align:center;position:relative}
.fstep .fdot{width:32px;height:32px;border-radius:50%;margin:0 auto 8px;display:flex;align-items:center;justify-content:center;border:2px solid rgba(255,255,255,0.1);background:rgba(0,0,0,0.4);z-index:1;position:relative;transition:all .4s cubic-bezier(0.4, 0, 0.2, 1);}
.fstep.done .fdot{background:var(--ok);border-color:var(--ok);color:var(--bg0);box-shadow:0 0 15px rgba(16,185,129,0.3);}
.fstep.cur .fdot{border-color:var(--ac);background:var(--acg);color:var(--ac);box-shadow:0 0 0 4px rgba(14,165,233,0.15);}
.fstep .flab{font-size:10px;font-weight:600;color:var(--t3);text-transform:uppercase;letter-spacing:0.04em;}
.fstep.done .flab,.fstep.cur .flab{color:var(--t1)}
.fstep:not(:last-child)::after{content:'';position:absolute;top:15px;left:50%;right:-50%;height:3px;background:rgba(255,255,255,0.05);z-index:0;transition:all 0.4s ease;}
.fstep.done:not(:last-child)::after{background:linear-gradient(90deg, var(--ac), rgba(14,165,233,0.3));}
.fsum{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;margin-bottom:24px}
.fbox{background:rgba(0,0,0,0.2);border:1px solid rgba(255,255,255,0.06);border-radius:var(--r);padding:18px;backdrop-filter:blur(4px);position:relative;overflow:hidden;}
.fbox::before{content:'';position:absolute;top:0;left:0;width:3px;height:100%;background:currentColor;opacity:0.5;}
.fbox .l{font-size:11px;color:var(--t2);margin-bottom:4px;font-weight:600;text-transform:uppercase;letter-spacing:0.04em;}
.fbox .v{font-family:var(--m);font-size:24px;font-weight:700}
.fbox .v.ok{color:var(--ok)}.fbox .v.wn{color:var(--wn)}.fbox .v.err{color:var(--err)}
.upload{border:2px dashed rgba(255,255,255,0.15);border-radius:var(--r);padding:30px;text-align:center;cursor:pointer;transition:all .3s ease;background:rgba(0,0,0,0.1);}
.upload:hover{border-color:var(--ac);background:var(--acg);transform:scale(1.02);}
.upload p{font-size:13px;font-weight:500;color:var(--t1)}.upload .h{font-size:11px;color:var(--t3);margin-top:4px}
.svc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:14px}
.svcc{background:rgba(0,0,0,0.2);border:1px solid rgba(255,255,255,0.08);border-radius:var(--r);padding:20px;text-align:center;cursor:pointer;transition:all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275);}
.svcc:hover{border-color:var(--ac);transform:translateY(-4px);box-shadow:0 10px 25px rgba(0,0,0,0.3);}
.svcc.sel{border-color:var(--ac);background:var(--acg);box-shadow:inset 0 0 0 1px var(--ac), 0 8px 20px rgba(14,165,233,0.2);}
.svcc .si2{width:48px;height:48px;margin:0 auto 12px;border-radius:14px;display:flex;align-items:center;justify-content:center;color:#fff;box-shadow:0 4px 12px rgba(0,0,0,0.3);}
.svcc .sn{font-family:var(--fh);font-size:14px;font-weight:600;margin-bottom:6px;color:var(--t1);}
.svcc .sp{font-family:var(--m);font-size:12px;color:var(--ok);font-weight:700}
.tabs{display:flex;gap:4px;border-bottom:1px solid rgba(255,255,255,0.06);margin-bottom:20px;padding:0 4px;}
.tab{padding:10px 20px;font-size:13px;font-weight:500;color:var(--t3);cursor:pointer;border-bottom:2px solid transparent;transition:all .2s ease;border-radius:8px 8px 0 0;}
.tab:hover{color:var(--t1);background:rgba(255,255,255,0.03);}
.tab.on{color:var(--ac);border-bottom-color:var(--ac);background:var(--acg);font-weight:600;}
.tgl{display:flex;align-items:center;justify-content:space-between;padding:12px 0;border-bottom:1px solid rgba(255,255,255,0.06)}
.tgl:last-child{border-bottom:none}
.tgl-s{width:40px;height:20px;border-radius:10px;background:rgba(255,255,255,0.2);cursor:pointer;position:relative;transition:all .3s ease;flex-shrink:0}
.tgl-s.on{background:var(--ac);box-shadow:0 0 10px var(--acg);}
.tgl-s::after{content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#0A0E17;transition:all .3s cubic-bezier(0.175, 0.885, 0.32, 1.275);}
.tgl-s.on::after{transform:translateX(20px)}
.empty{text-align:center;padding:40px 20px;color:var(--t3)}
.empty h4{font-family:var(--fh);font-size:16px;font-weight:600;margin-bottom:6px;color:var(--t2)}
.empty p{font-size:13px}
.sort-i{display:inline-flex;margin-left:4px;opacity:.3;transition:0.2s;}
.tb th.so .sort-i{opacity:1;color:var(--ac)}
.fbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:20px;background:rgba(0,0,0,0.15);padding:12px;border-radius:12px;border:1px solid rgba(255,255,255,0.05);}
.fbar .lbl{font-size:10px;font-weight:700;color:var(--t3);text-transform:uppercase;letter-spacing:.08em;margin-right:4px}
.pwd-wrap{position:relative;display:flex;align-items:center}
.pwd-wrap input{padding-right:40px}
.pwd-toggle{position:absolute;right:12px;cursor:pointer;color:var(--t3);display:flex;transition:0.2s;}
.pwd-toggle:hover{color:var(--t1)}
.file-slot{background:rgba(0,0,0,0.2);border:1px solid rgba(255,255,255,0.08);border-radius:var(--rs);padding:14px;margin-bottom:10px;display:flex;align-items:center;gap:12px;transition:all .2s ease}
.file-slot:hover{border-color:rgba(255,255,255,0.2);background:rgba(0,0,0,0.3);}
.file-slot .fs-label{font-size:12px;font-weight:600;color:var(--t2);min-width:150px}
.file-slot .fs-name{font-family:var(--m);font-size:12px;color:var(--t1);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.file-slot .fs-actions{display:flex;gap:6px}
.scan-mini-viewer{width:68px;height:60px;flex-shrink:0;background:linear-gradient(135deg,rgba(14,165,233,0.08),rgba(139,92,246,0.1));border:1px solid rgba(14,165,233,0.3);border-radius:var(--rs);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;animation:fi .25s ease-out;}
.scan-mini-viewer .smv-icon{color:var(--ac);}
.scan-mini-viewer .smv-ext{font-family:var(--m);font-size:9px;font-weight:700;color:var(--ac);letter-spacing:.1em;opacity:.75;}
.preview-area{background:rgba(0,0,0,0.3);border:1px solid rgba(255,255,255,0.08);border-radius:var(--r);min-height:200px;display:flex;align-items:stretch;justify-content:center;margin-top:14px;position:relative;overflow:hidden;}
.preview-area img{max-width:100%;max-height:350px;object-fit:contain;animation:fi 0.3s ease-out;}
.preview-area .ph{color:var(--t3);font-size:13px;text-align:center}
.confirm-box{background:rgba(245,158,11,0.05);border:1px solid rgba(245,158,11,0.2);border-radius:var(--r);padding:20px;margin-bottom:16px}
.confirm-box h4{font-size:14px;font-weight:600;margin-bottom:12px;color:var(--wn);display:flex;align-items:center;gap:8px;}
::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.15);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.25)}
.toast-container{position:fixed;top:20px;right:20px;display:flex;flex-direction:column;gap:10px;z-index:9999; pointer-events: none;}
.toast{background:rgba(30, 41, 59, 0.9);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,0.1);color:#fff;padding:12px 20px;border-radius:12px;box-shadow:0 10px 30px rgba(0,0,0,0.3);display:flex;align-items:center;gap:12px;font-family:var(--f);font-size:13px;font-weight:500;animation:toast-in 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; pointer-events: auto;}
.toast.hide{animation: toast-out 0.3s ease-in forwards;}
.toast.success{border-left:4px solid var(--ok); background:linear-gradient(90deg, rgba(16,185,129,0.1) 0%, rgba(30,41,59,0.9) 100%);}
.toast.error{border-left:4px solid var(--err); background:linear-gradient(90deg, rgba(239,68,68,0.1) 0%, rgba(30,41,59,0.9) 100%);}
.toast.info{border-left:4px solid var(--ac); background:linear-gradient(90deg, rgba(14,165,233,0.1) 0%, rgba(30,41,59,0.9) 100%);}
.toast-icon{display:flex; align-items:center; justify-content:center;}
@keyframes toast-in{from{opacity:0;transform:translateY(-20px) scale(0.9)}to{opacity:1;transform:translateY(0) scale(1)}}
@keyframes toast-out{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-20px)}}
.login-page{display:flex;justify-content:center;align-items:center;min-height:100vh;background:var(--bg0);padding:20px;background: radial-gradient(circle at 50% 30%, #172136 0%, var(--bg0) 70%);}
.login-card{width:400px;max-width:100%;padding:40px 30px;}
.login-logo{display:flex;align-items:center;justify-content:center;gap:12px;margin-bottom:30px;}
.login-logo .logo-icon{width:48px;height:48px;background:linear-gradient(135deg,var(--ac),#0284C7);border-radius:14px;display:flex;align-items:center;justify-content:center;color:#fff;box-shadow:0 6px 20px var(--acg);}
.login-logo h1{font-family:var(--fh);font-size:22px;font-weight:700;color:var(--t1);}
.login-logo .sub{font-size:10px;color:var(--t3);font-weight:600;letter-spacing:.08em;text-transform:uppercase;}
.verif-code{display:flex;gap:8px;justify-content:center;margin:20px 0;}
.verif-code input{width:48px;height:56px;text-align:center;font-family:var(--m);font-size:22px;font-weight:700;background:rgba(0,0,0,0.3);border:2px solid rgba(255,255,255,0.1);border-radius:12px;color:var(--t1);outline:none;transition:0.3s;}
.verif-code input:focus{border-color:var(--ac);box-shadow:0 0 0 3px var(--acg);}
.link-btn{background:none;border:none;color:var(--ac);font-family:var(--f);font-size:12px;cursor:pointer;padding:4px;font-weight:500;transition:0.2s;}
.link-btn:hover{color:#38BDF8;text-decoration:underline;}
.sbox{display:flex;align-items:center;gap:8px;background:rgba(0,0,0,0.2);border:1px solid rgba(255,255,255,0.08);border-radius:var(--rs);padding:8px 14px;color:var(--t3);width:260px;transition:all .3s ease}
.sbox:focus-within{border-color:var(--ac);box-shadow:0 0 0 2px var(--acg);background:rgba(0,0,0,0.3);}
.sbox input{border:none;background:none;color:var(--t1);font-family:var(--f);font-size:13px;width:100%;outline:none;transition:0.2s}
.sbox input::placeholder{color:var(--t3)}
@media(max-width:768px){
  .sb{position:fixed;left:0;top:0;bottom:0;z-index:99;transform:translateX(-100%);}
  .sb.open{transform:translateX(0);}
  .sb-overlay.open{display:block;z-index:98;}
  .hamburger{display:flex;}
  .stats{grid-template-columns:1fr 1fr}
  .fsum{grid-template-columns:1fr}
  .fr,.fr3{grid-template-columns:1fr}
  .pipe{flex-direction:column}.pcol{min-width:100%}
  .pg{padding:16px}
  .top{padding:0 16px}
  .top-t{font-size:14px}
  .cd-h{padding:14px 16px;gap:8px}
  .cd-h h3{font-size:13px}
  .ml{width:96vw;max-height:92vh;border-radius:14px}
  .ml-b{padding:16px}
  .tb td,.tb th{padding:10px 12px;font-size:11px}
  .login-card{padding:24px 20px;}
}
`;

// ═══ HELPERS ═══
const fmt=v=>`R$${Number(v).toLocaleString('pt-BR')}`;
const fmtD=d=>d?new Date(d+'T12:00:00').toLocaleDateString('pt-BR'):'—';
const StB=({id})=>{const s=STM[id];return s?<span className="badge" style={{background:`${s.color}15`,color:s.color}}><span className="dt" style={{background:s.color}}/>{s.label}</span>:null;};
const RB=({id})=>{const r=RM[id];return r?<span className="chip" style={{background:`${r.color}15`,color:r.color}}>{r.label}</span>:null;};
const Modal=({title,children,onClose,footer,large})=><div className="mo" onClick={onClose}><div className={`ml${large?' lg':''}`} onClick={e=>e.stopPropagation()}><div className="ml-h"><h3>{title}</h3><button className="ibtn" onClick={onClose}>{I.close}</button></div><div className="ml-b">{children}</div>{footer&&<div className="ml-f">{footer}</div>}</div></div>;
const FlowT=({status})=>{const ci=STATUS_FLOW.findIndex(s=>s.id===status);return <div className="sflow">{STATUS_FLOW.map((s,i)=><div key={s.id} className={`fstep${i<ci?' done':''}${i===ci?' cur':''}`}><div className="fdot">{i<ci?I.check:<span style={{fontSize:9,fontWeight:700,color:i===ci?'var(--ac)':'var(--t3)'}}>{i+1}</span>}</div><div className="flab">{s.label}</div></div>)}</div>;};
const STh=({label,field,sort,onSort})=><th className={sort.field===field?'so':''} onClick={()=>onSort(field)}>{label}<span className="sort-i">{sort.field===field&&sort.dir==='asc'?I.up:I.down}</span></th>;
const cpfMask=(v)=>{const n=v.replace(/\D/g,'').slice(0,11);if(n.length<=3)return n;if(n.length<=6)return n.slice(0,3)+'.'+n.slice(3);if(n.length<=9)return n.slice(0,3)+'.'+n.slice(3,6)+'.'+n.slice(6);return n.slice(0,3)+'.'+n.slice(3,6)+'.'+n.slice(6,9)+'-'+n.slice(9);};

// Payment confirmation modal
function PayConfirm({title,amount,onConfirm,onCancel}){
  const[method,setM]=useState('');
  return <Modal title="Confirmar Pagamento" onClose={onCancel} footer={<><button className="btn bs" onClick={onCancel}>Cancelar</button><button className="btn bp" disabled={!method} onClick={()=>onConfirm(method)}>Confirmar Baixa</button></>}>
    <div className="confirm-box"><h4>⚠ Confirmar baixa de pagamento?</h4>
    <div style={{fontSize:12,color:'var(--t2)',marginBottom:6}}>{title}</div>
    <div style={{fontFamily:'var(--m)',fontSize:20,fontWeight:700,color:'var(--ac)'}}>{fmt(amount)}</div></div>
    <div className="fg"><label className="fl">Forma de Pagamento *</label>
    <select className="fs" value={method} onChange={e=>setM(e.target.value)}><option value="">Selecione...</option>{PAY_METHODS.map(m=><option key={m} value={m}>{m}</option>)}</select></div>
  </Modal>;
}


// ═══ TOAST CONTEXT ═══
const ToastContext = createContext(null);
const useToast = () => useContext(ToastContext);

function ToastProvider({ children }){
  const [toasts, setToasts] = useState([]);
  
  const addToast = useCallback((msg, type = 'info') => {
    const id = Date.now() + Math.random();
    setToasts(prev => [...prev, { id, msg, type, status: 'show' }]);
    setTimeout(() => {
      setToasts(prev => prev.map(t => t.id === id ? { ...t, status: 'hide' } : t));
      setTimeout(() => {
        setToasts(prev => prev.filter(t => t.id !== id));
      }, 300);
    }, 3000);
  }, []);

  return (
    <ToastContext.Provider value={{ addToast }}>
      {children}
      <div className="toast-container">
        {toasts.map(t => (
          <div key={t.id} className={`toast ${t.type} ${t.status}`}>
            <div className="toast-icon">
              {t.type === 'success' ? I.check : (t.type === 'error' ? I.close : I.bell)}
            </div>
            <div>{t.msg}</div>
          </div>
        ))}
      </div>
    </ToastContext.Provider>
  );
}

// ═══ LOGIN / REGISTER / FORGOT ═══

function PgLogin({ onLog }) {
  const [view, setView] = useState('login'); // login, register, verify, forgot, reset
  const [loginType, setLT] = useState('empresa'); // empresa | cliente
  const [email, setEmail] = useState(''); const [pass, setPass] = useState(''); const [err, setErr] = useState(''); const [showPw, setSP] = useState(false);
  const [regForm, setRF] = useState({name:'',cpf:'',cro:'',clinic:'',phone:'',email:'',password:''});
  const [verifyEmail, setVE] = useState('');
  const [code, setCode] = useState(['','','','','','']);
  const [forgotEmail, setFE] = useState('');
  const [resetForm, setResetF] = useState({email:'',code:'',password:''});
  const [loading, setLoading] = useState(false);
  const [devCode, setDevCode] = useState('');
  const { addToast } = useToast();
  const codeRefs = [useRef(),useRef(),useRef(),useRef(),useRef(),useRef()];

  const doLogin = async (e) => {
    e.preventDefault(); setErr(''); setLoading(true);
    try {
      const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password: pass }) });
      const data = await res.json();
      if (!res.ok) { setErr(data.error); setLoading(false); return; }
      localStorage.setItem('aptoken', data.token); localStorage.setItem('apmode', data.mode); localStorage.setItem('apuser', JSON.stringify(data.user || data.client));
      onLog(data.token, data.mode, data.user || data.client); addToast('Bem-vindo!', 'success');
    } catch(e) { setErr(e.message); } finally { setLoading(false); }
  };

  const doRegister = async (e) => {
    e.preventDefault(); setErr(''); setLoading(true);
    try {
      // Check CPF
      const cpfClean = regForm.cpf.replace(/\D/g, '');
      if (cpfClean.length !== 11) { setErr('CPF deve ter 11 dígitos'); setLoading(false); return; }
      const cpfRes = await fetch(`/api/auth/check-cpf/${cpfClean}`);
      const cpfData = await cpfRes.json();
      if (cpfData.exists) { setErr('CPF já cadastrado no sistema. Se você já possui conta, use "Esqueci minha senha".'); setLoading(false); return; }

      const res = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(regForm) });
      const data = await res.json();
      if (!res.ok) { setErr(data.error); setLoading(false); return; }
      setVE(regForm.email);
      if (data.devCode) setDevCode(data.devCode);
      setView('verify');
      addToast('Código de verificação enviado para seu email!', 'info');
    } catch(e) { setErr(e.message); } finally { setLoading(false); }
  };

  const doVerify = async () => {
    setErr(''); setLoading(true);
    try {
      const codeStr = code.join('');
      const res = await fetch('/api/auth/verify-email', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: verifyEmail, code: codeStr }) });
      const data = await res.json();
      if (!res.ok) { setErr(data.error); setLoading(false); return; }
      addToast('Email verificado! Faça login agora.', 'success');
      setView('login'); setEmail(verifyEmail);
    } catch(e) { setErr(e.message); } finally { setLoading(false); }
  };

  const doForgot = async (e) => {
    e.preventDefault(); setErr(''); setLoading(true);
    try {
      const res = await fetch('/api/auth/forgot-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: forgotEmail }) });
      const data = await res.json();
      if (!res.ok) { setErr(data.error); setLoading(false); return; }
      if (data.devCode) setDevCode(data.devCode);
      setResetF({...resetForm, email: forgotEmail});
      setView('reset');
      addToast('Se o email estiver cadastrado, você receberá um código.', 'info');
    } catch(e) { setErr(e.message); } finally { setLoading(false); }
  };

  const doReset = async (e) => {
    e.preventDefault(); setErr(''); setLoading(true);
    try {
      const res = await fetch('/api/auth/reset-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: resetForm.email, code: resetForm.code, newPassword: resetForm.password }) });
      const data = await res.json();
      if (!res.ok) { setErr(data.error); setLoading(false); return; }
      addToast('Senha alterada com sucesso!', 'success');
      setView('login'); setEmail(resetForm.email);
    } catch(e) { setErr(e.message); } finally { setLoading(false); }
  };

  const handleCodeInput = (i, val) => {
    if (val.length > 1) val = val.slice(-1);
    const nc = [...code]; nc[i] = val; setCode(nc);
    if (val && i < 5) codeRefs[i+1].current?.focus();
  };

  const LoginLogo = () => <div className="login-logo"><div className="logo-icon">{I.stl}</div><div><h1>AP Core Planning</h1><div className="sub">Laboratório Digital</div></div></div>;

  if (view === 'verify') return <div className="login-page"><div className="cd login-card">
    <LoginLogo />
    <div style={{textAlign:'center',marginBottom:20}}><div style={{fontSize:14,fontWeight:600,marginBottom:6}}>Verificação de Email</div><div style={{fontSize:12,color:'var(--t2)'}}>Insira o código de 6 dígitos enviado para <strong>{verifyEmail}</strong></div></div>
    {devCode && <div style={{background:'var(--acg)',border:'1px solid var(--ac)',borderRadius:8,padding:10,marginBottom:16,textAlign:'center'}}><div style={{fontSize:10,color:'var(--t3)'}}>DEV MODE — Código:</div><div style={{fontFamily:'var(--m)',fontSize:22,fontWeight:700,color:'var(--ac)',letterSpacing:4}}>{devCode}</div></div>}
    <div className="verif-code">{code.map((c,i)=><input key={i} ref={codeRefs[i]} maxLength={1} value={c} onChange={e=>handleCodeInput(i,e.target.value)} onKeyDown={e=>{if(e.key==='Backspace'&&!c&&i>0)codeRefs[i-1].current?.focus();}}/>)}</div>
    {err && <div style={{color:'var(--err)',fontSize:12,textAlign:'center',marginBottom:10}}>{err}</div>}
    <button className="btn bp" style={{width:'100%',justifyContent:'center',marginTop:10}} onClick={doVerify} disabled={loading||code.join('').length<6}>Verificar</button>
    <div style={{textAlign:'center',marginTop:14}}><button className="link-btn" onClick={()=>{setView('login');setErr('');}}>← Voltar ao login</button></div>
  </div></div>;

  if (view === 'register') return <div className="login-page"><div className="cd login-card">
    <LoginLogo />
    <div style={{textAlign:'center',marginBottom:20,fontSize:14,fontWeight:600}}>Criar Conta</div>
    <form onSubmit={doRegister} style={{display:'flex',flexDirection:'column',gap:12}}>
      <div><label className="fl">Nome Completo *</label><input className="fi" value={regForm.name} onChange={e=>setRF({...regForm,name:e.target.value})} required/></div>
      <div><label className="fl">CPF *</label><input className="fi" value={regForm.cpf} onChange={e=>setRF({...regForm,cpf:cpfMask(e.target.value)})} placeholder="000.000.000-00" required/></div>
      <div className="fr"><div><label className="fl">CRO</label><input className="fi" value={regForm.cro} onChange={e=>setRF({...regForm,cro:e.target.value})} placeholder="CRO-UF 00000"/></div>
      <div><label className="fl">Clínica</label><input className="fi" value={regForm.clinic} onChange={e=>setRF({...regForm,clinic:e.target.value})}/></div></div>
      <div className="fr"><div><label className="fl">Telefone</label><input className="fi" value={regForm.phone} onChange={e=>setRF({...regForm,phone:e.target.value})}/></div>
      <div><label className="fl">Email *</label><input className="fi" type="email" value={regForm.email} onChange={e=>setRF({...regForm,email:e.target.value})} required/></div></div>
      <div><label className="fl">Senha *</label><div className="pwd-wrap"><input className="fi" type={showPw?'text':'password'} value={regForm.password} onChange={e=>setRF({...regForm,password:e.target.value})} required minLength={6}/><span className="pwd-toggle" onClick={()=>setSP(!showPw)}>{showPw?I.eyeOff:I.eye}</span></div></div>
      {err && <div style={{color:'var(--err)',fontSize:12}}>{err}</div>}
      <button className="btn bp" style={{justifyContent:'center'}} type="submit" disabled={loading}>{loading?'Enviando...':'Criar Conta'}</button>
    </form>
    <div style={{textAlign:'center',marginTop:14}}><button className="link-btn" onClick={()=>{setView('login');setErr('');}}>← Já tenho conta</button></div>
  </div></div>;

  if (view === 'forgot') return <div className="login-page"><div className="cd login-card">
    <LoginLogo />
    <div style={{textAlign:'center',marginBottom:20}}><div style={{fontSize:14,fontWeight:600,marginBottom:6}}>Recuperar Senha</div><div style={{fontSize:12,color:'var(--t2)'}}>Informe seu email para receber um código de recuperação</div></div>
    <form onSubmit={doForgot} style={{display:'flex',flexDirection:'column',gap:16}}>
      <div><label className="fl">Email</label><input className="fi" type="email" value={forgotEmail} onChange={e=>setFE(e.target.value)} required/></div>
      {err && <div style={{color:'var(--err)',fontSize:12}}>{err}</div>}
      <button className="btn bp" style={{justifyContent:'center'}} type="submit" disabled={loading}>{loading?'Enviando...':'Enviar Código'}</button>
    </form>
    <div style={{textAlign:'center',marginTop:14}}><button className="link-btn" onClick={()=>{setView('login');setErr('');}}>← Voltar ao login</button></div>
  </div></div>;

  if (view === 'reset') return <div className="login-page"><div className="cd login-card">
    <LoginLogo />
    <div style={{textAlign:'center',marginBottom:20}}><div style={{fontSize:14,fontWeight:600,marginBottom:6}}>Redefinir Senha</div><div style={{fontSize:12,color:'var(--t2)'}}>Insira o código recebido e sua nova senha</div></div>
    {devCode && <div style={{background:'var(--acg)',border:'1px solid var(--ac)',borderRadius:8,padding:10,marginBottom:16,textAlign:'center'}}><div style={{fontSize:10,color:'var(--t3)'}}>DEV MODE — Código:</div><div style={{fontFamily:'var(--m)',fontSize:22,fontWeight:700,color:'var(--ac)',letterSpacing:4}}>{devCode}</div></div>}
    <form onSubmit={doReset} style={{display:'flex',flexDirection:'column',gap:16}}>
      <div><label className="fl">Código</label><input className="fi" value={resetForm.code} onChange={e=>setResetF({...resetForm,code:e.target.value})} placeholder="000000" required/></div>
      <div><label className="fl">Nova Senha</label><div className="pwd-wrap"><input className="fi" type={showPw?'text':'password'} value={resetForm.password} onChange={e=>setResetF({...resetForm,password:e.target.value})} required minLength={6}/><span className="pwd-toggle" onClick={()=>setSP(!showPw)}>{showPw?I.eyeOff:I.eye}</span></div></div>
      {err && <div style={{color:'var(--err)',fontSize:12}}>{err}</div>}
      <button className="btn bp" style={{justifyContent:'center'}} type="submit" disabled={loading}>{loading?'Salvando...':'Redefinir Senha'}</button>
    </form>
    <div style={{textAlign:'center',marginTop:14}}><button className="link-btn" onClick={()=>{setView('login');setErr('');}}>← Voltar ao login</button></div>
  </div></div>;

  // Login view
  return <div className="login-page"><div className="cd login-card">
    <LoginLogo />
    <div className="tabs" style={{marginBottom:20}}>
      <div className={`tab ${loginType==='empresa'?'on':''}`} onClick={()=>{setLT('empresa');setErr('');}}>Empresa</div>
      <div className={`tab ${loginType==='cliente'?'on':''}`} onClick={()=>{setLT('cliente');setErr('');}}>Cliente</div>
    </div>
    <form onSubmit={doLogin} style={{display:'flex',flexDirection:'column',gap:16}}>
      <div><label className="fl">Email</label><input className="fi" type="email" value={email} onChange={e=>setEmail(e.target.value)} placeholder="seu@email.com" required/></div>
      <div><label className="fl">Senha</label><div className="pwd-wrap"><input className="fi" type={showPw?'text':'password'} value={pass} onChange={e=>setPass(e.target.value)} required/><span className="pwd-toggle" onClick={()=>setSP(!showPw)}>{showPw?I.eyeOff:I.eye}</span></div></div>
      {err && <div style={{color:'var(--err)',fontSize:12}}>{err}</div>}
      <button className="btn bp" style={{justifyContent:'center',marginTop:4}} type="submit" disabled={loading}>{loading?'Entrando...':'Entrar'}</button>
    </form>
    <div style={{display:'flex',justifyContent:'space-between',marginTop:16}}>
      <button className="link-btn" onClick={()=>{setView('forgot');setErr('');}}>Esqueci minha senha</button>
      {loginType==='cliente' && <button className="link-btn" onClick={()=>{setView('register');setErr('');}}>Criar conta →</button>}
    </div>
  </div></div>;
}

// ═══ ADMIN PAGES ═══

// Dashboard
function PgDash({jobs,clients,users,services}){
  const SvcN=({id})=>services.find(s=>s.id===id)?.name||id;
  const tR=jobs.filter(j=>j.paid).reduce((s,j)=>s+j.value,0);const pR=jobs.filter(j=>!j.paid).reduce((s,j)=>s+j.value,0);const active=jobs.filter(j=>!['entregue','finalizado'].includes(j.status)).length;const un=jobs.filter(j=>!j.assignedTo).length;
  const recent=[...jobs].sort((a,b)=>b.date.localeCompare(a.date)).slice(0,6);const cN=id=>clients.find(c=>c.id===id)?.name||'—';
  return <>
    <div className="stats"><div className="st"><div className="sh"><div className="ico" style={{background:'rgba(14,165,233,.1)',color:'var(--ac)'}}>{I.work}</div></div><div className="sv">{jobs.length}</div><div className="sl">Total Trabalhos</div></div>
    <div className="st"><div className="sh"><div className="ico" style={{background:'rgba(16,185,129,.1)',color:'var(--ok)'}}>{I.money}</div></div><div className="sv">{fmt(tR)}</div><div className="sl">Receita</div><div style={{marginTop:'auto', height:4, background:'rgba(255,255,255,0.05)', borderRadius:2, overflow:'hidden'}}><div style={{height:'100%', background:'var(--ok)', width:`${(tR/(tR+pR||1))*100}%`}}></div></div></div>
    <div className="st"><div className="sh"><div className="ico" style={{background:'rgba(245,158,11,.1)',color:'var(--wn)'}}>{I.flow}</div></div><div className="sv">{active}</div><div className="sl">Em Andamento</div></div>
    <div className="st"><div className="sh"><div className="ico" style={{background:'rgba(239,68,68,.1)',color:'var(--err)'}}>{I.money}</div></div><div className="sv">{fmt(pR)}</div><div className="sl">Pendente</div><div style={{marginTop:'auto', height:4, background:'rgba(255,255,255,0.05)', borderRadius:2, overflow:'hidden'}}><div style={{height:'100%', background:'var(--err)', width:`${(pR/(tR+pR||1))*100}%`}}></div></div></div>
    <div className="st"><div className="sh"><div className="ico" style={{background:'rgba(139,92,246,.1)',color:'var(--pu)'}}>{I.assign}</div></div><div className="sv">{un}</div><div className="sl">Sem Atribuição</div></div></div>
    <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12}}>
    <div className="cd"><div className="cd-h"><h3>Recentes</h3></div><div className="tb-wrap"><table className="tb"><thead><tr><th>ID</th><th>Cliente</th><th>Serviço</th><th>Status</th></tr></thead><tbody>{recent.map(j=><tr key={j.id}><td style={{fontFamily:'var(--m)',fontSize:10}}>{j.id}</td><td style={{color:'var(--t1)',fontWeight:500}}>{cN(j.clientId)}</td><td><SvcN id={j.service}/></td><td><StB id={j.status}/></td></tr>)}</tbody></table></div></div>
    <div className="cd"><div className="cd-h"><h3>Top Clientes</h3></div><div className="tb-wrap"><table className="tb"><thead><tr><th>Cliente</th><th>Trab.</th><th>Receita</th></tr></thead><tbody>{clients.map(c=>{const cj=jobs.filter(j=>j.clientId===c.id);return <tr key={c.id}><td><div style={{color:'var(--t1)',fontWeight:500}}>{c.name}</div><div style={{fontSize:9,color:'var(--t3)'}}>{c.clinic}</div></td><td style={{fontFamily:'var(--m)'}}>{cj.length}</td><td style={{fontFamily:'var(--m)',color:'var(--ok)',fontWeight:600}}>{fmt(cj.filter(j=>j.paid).reduce((s,j)=>s+j.value,0))}</td></tr>;})}</tbody></table></div></div>
    </div></>;
}

// Service Categories
function PgServiceCategories({categories,setCategories,token}){
  const[modal,setMo]=useState(null);
  const empty={name:'',description:'',color:'#0EA5E9'};
  const[form,setF]=useState(empty);
  const { addToast } = useToast();
  const open=(c)=>{setF(c==='new'?empty:{name:c.name,description:c.description||'',color:c.color||'#0EA5E9'});setMo(c);};
  const save=async()=>{
    try{
      if(modal==='new'){
        const res=await fetch('/api/service-categories',{method:'POST',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify(form)});
        const data=await res.json();if(!res.ok)throw new Error(data.error);
        setCategories(p=>[...p,{id:data.id,...form,active:1}]);addToast('Categoria criada!','success');
      }else{
        const res=await fetch(`/api/service-categories/${modal.id}`,{method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({...form,active:1})});
        if(!res.ok)throw new Error('Erro');
        setCategories(p=>p.map(c=>c.id===modal.id?{...c,...form}:c));addToast('Categoria atualizada!','success');
      }
      setMo(null);
    }catch(e){addToast(e.message,'error');}
  };
  return <><div className="cd"><div className="cd-h"><h3>Categorias de Serviço ({categories.length})</h3><button className="btn bp" onClick={()=>open('new')}>{I.plus} Nova Categoria</button></div>
    <div className="tb-wrap"><table className="tb"><thead><tr><th>Cor</th><th>Nome</th><th>Descrição</th><th>Status</th><th>Ações</th></tr></thead><tbody>{categories.map(c=><tr key={c.id}>
      <td><div style={{width:24,height:24,borderRadius:8,background:c.color||'var(--ac)'}}></div></td>
      <td style={{color:'var(--t1)',fontWeight:500}}>{c.name}</td>
      <td style={{fontSize:12,color:'var(--t2)',maxWidth:250,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{c.description||'—'}</td>
      <td><span className="chip" style={{background:c.active?'rgba(16,185,129,.1)':'rgba(239,68,68,.1)',color:c.active?'var(--ok)':'var(--err)'}}>{c.active?'Ativa':'Inativa'}</span></td>
      <td><div style={{display:'flex',gap:4}}><button className="btn bg bsm" onClick={()=>open(c)}>{I.edit} Editar</button></div></td>
    </tr>)}</tbody></table></div></div>
    {modal&&<Modal title={modal==='new'?'Nova Categoria':'Editar Categoria'} onClose={()=>setMo(null)} footer={<><button className="btn bs" onClick={()=>setMo(null)}>Cancelar</button><button className="btn bp" onClick={save}>Salvar</button></>}>
    <div className="fg"><label className="fl">Nome</label><input className="fi" value={form.name} onChange={e=>setF({...form,name:e.target.value})}/></div>
    <div className="fg"><label className="fl">Descrição</label><textarea className="fta" rows={2} value={form.description} onChange={e=>setF({...form,description:e.target.value})}/></div>
    <div className="fg"><label className="fl">Cor</label><div style={{display:'flex',gap:8,alignItems:'center'}}><input type="color" value={form.color} onChange={e=>setF({...form,color:e.target.value})} style={{width:40,height:36,border:'none',background:'transparent',cursor:'pointer'}}/><input className="fi" style={{width:120}} value={form.color} onChange={e=>setF({...form,color:e.target.value})}/></div></div>
    </Modal>}</>;
}

// Service Types
function PgServiceTypes({services,setServices,categories,token}){
  const[modal,setMo]=useState(null);
  const empty={id:'',name:'',base_price:'',color:'#0EA5E9',category_id:''};
  const[form,setF]=useState(empty);
  const { addToast } = useToast();
  const open=(s)=>{setF(s==='new'?{...empty,category_id:categories[0]?.id||''}:{id:s.id,name:s.name,base_price:s.base_price,color:s.color||'#0EA5E9',category_id:s.category_id||''});setMo(s);};
  const save=async()=>{
    try{
      if(modal==='new'){
        const res=await fetch('/api/services',{method:'POST',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({...form,base_price:Number(form.base_price)||0})});
        const data=await res.json();if(!res.ok)throw new Error(data.error);
        const cat=categories.find(c=>c.id===Number(form.category_id));
        setServices(p=>[...p,{...form,base_price:Number(form.base_price)||0,active:1,category_name:cat?.name,category_color:cat?.color}]);addToast('Serviço criado!','success');
      }else{
        const res=await fetch(`/api/services/${modal.id}`,{method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({name:form.name,base_price:Number(form.base_price)||0,color:form.color,category_id:Number(form.category_id)||null,active:1})});
        if(!res.ok)throw new Error('Erro');
        const cat=categories.find(c=>c.id===Number(form.category_id));
        setServices(p=>p.map(s=>s.id===modal.id?{...s,name:form.name,base_price:Number(form.base_price)||0,color:form.color,category_id:Number(form.category_id)||null,category_name:cat?.name,category_color:cat?.color}:s));addToast('Serviço atualizado!','success');
      }
      setMo(null);
    }catch(e){addToast(e.message,'error');}
  };
  return <><div className="cd"><div className="cd-h"><h3>Tipos de Serviço ({services.length})</h3><button className="btn bp" onClick={()=>open('new')}>{I.plus} Novo Serviço</button></div>
    <div className="tb-wrap"><table className="tb"><thead><tr><th>Cor</th><th>ID</th><th>Nome</th><th>Categoria</th><th>Preço Base</th><th>Status</th><th>Ações</th></tr></thead><tbody>{services.map(s=><tr key={s.id}>
      <td><div style={{width:24,height:24,borderRadius:8,background:s.color||'var(--ac)'}}></div></td>
      <td style={{fontFamily:'var(--m)',fontSize:10}}>{s.id}</td>
      <td style={{color:'var(--t1)',fontWeight:500}}>{s.name}</td>
      <td>{s.category_name?<span className="chip" style={{background:`${s.category_color||'var(--ac)'}15`,color:s.category_color||'var(--ac)'}}>{s.category_name}</span>:'—'}</td>
      <td style={{fontFamily:'var(--m)',fontWeight:600,color:'var(--ok)'}}>{fmt(s.base_price)}</td>
      <td><span className="chip" style={{background:s.active?'rgba(16,185,129,.1)':'rgba(239,68,68,.1)',color:s.active?'var(--ok)':'var(--err)'}}>{s.active?'Ativo':'Inativo'}</span></td>
      <td><div style={{display:'flex',gap:4}}><button className="btn bg bsm" onClick={()=>open(s)}>{I.edit} Editar</button></div></td>
    </tr>)}</tbody></table></div></div>
    {modal&&<Modal title={modal==='new'?'Novo Serviço':'Editar Serviço'} onClose={()=>setMo(null)} footer={<><button className="btn bs" onClick={()=>setMo(null)}>Cancelar</button><button className="btn bp" onClick={save}>Salvar</button></>}>
    {modal==='new'&&<div className="fg"><label className="fl">ID (identificador único, sem espaços)</label><input className="fi" value={form.id} onChange={e=>setF({...form,id:e.target.value.toLowerCase().replace(/[^a-z0-9_]/g,'')})}/></div>}
    <div className="fg"><label className="fl">Nome</label><input className="fi" value={form.name} onChange={e=>setF({...form,name:e.target.value})}/></div>
    <div className="fr"><div className="fg"><label className="fl">Categoria</label><select className="fs" value={form.category_id} onChange={e=>setF({...form,category_id:e.target.value})}><option value="">Sem categoria</option>{categories.filter(c=>c.active).map(c=><option key={c.id} value={c.id}>{c.name}</option>)}</select></div>
    <div className="fg"><label className="fl">Preço Base (R$)</label><input className="fi" type="number" value={form.base_price} onChange={e=>setF({...form,base_price:e.target.value})}/></div></div>
    <div className="fg"><label className="fl">Cor</label><div style={{display:'flex',gap:8,alignItems:'center'}}><input type="color" value={form.color} onChange={e=>setF({...form,color:e.target.value})} style={{width:40,height:36,border:'none',background:'transparent',cursor:'pointer'}}/><input className="fi" style={{width:120}} value={form.color} onChange={e=>setF({...form,color:e.target.value})}/></div></div>
    </Modal>}</>;
}

// Users
function PgUsers({users,setUsers,token}){
  const[modal,setMo]=useState(null);
  const empty={name:'',email:'',role:'tecnico',active:true,password:''};
  const[form,setF]=useState(empty);
  const open=(u)=>{setF(u==='new'?empty:{name:u.name,email:u.email,role:u.role,active:u.active,password:''});setMo(u);};
  const { addToast } = useToast();
  const save=async()=>{
    try{
      if(modal==='new'){
        const res=await fetch('/api/users',{method:'POST',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify(form)});
        const data=await res.json();if(!res.ok)throw new Error(data.error);
        setUsers(p=>[...p,{id:data.id,...form}]);addToast('Usuário cadastrado!','success');
      }else{
        const res=await fetch(`/api/users/${modal.id}`,{method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify(form)});
        if(!res.ok)throw new Error('Erro');
        setUsers(p=>p.map(u=>u.id===modal.id?{...u,...form}:u));addToast('Usuário atualizado!','success');
      }
      setMo(null);
    }catch(e){addToast(e.message,'error');}
  };
  return <><div className="cd"><div className="cd-h"><h3>Usuários</h3><button className="btn bp" onClick={()=>open('new')}>{I.plus} Novo</button></div>
    <div className="tb-wrap"><table className="tb"><thead><tr><th>Nome</th><th>Email</th><th>Função</th><th>Status</th><th>Ações</th></tr></thead><tbody>{users.map(u=><tr key={u.id}><td style={{color:'var(--t1)',fontWeight:500}}>{u.name}</td><td style={{fontFamily:'var(--m)',fontSize:10}}>{u.email}</td><td><RB id={u.role}/></td><td><span className="chip" style={{background:u.active?'rgba(16,185,129,.1)':'rgba(239,68,68,.1)',color:u.active?'var(--ok)':'var(--err)'}}>{u.active?'Ativo':'Inativo'}</span></td><td><div style={{display:'flex',gap:4}}><button className="btn bg bsm" onClick={()=>open(u)}>{I.edit} Editar</button><button className="btn bg bsm" onClick={async()=>{const na=!u.active;try{const r=await fetch(`/api/users/${u.id}`,{method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({name:u.name,email:u.email,role:u.role,active:na})});if(!r.ok)throw new Error('Erro');setUsers(p=>p.map(x=>x.id===u.id?{...x,active:na}:x));}catch(e){}}}>{u.active?'⏸':'▶'}</button></div></td></tr>)}</tbody></table></div></div>
    {modal&&<Modal title={modal==='new'?'Novo Usuário':'Editar Usuário'} onClose={()=>setMo(null)} footer={<><button className="btn bs" onClick={()=>setMo(null)}>Cancelar</button><button className="btn bp" onClick={save}>Salvar</button></>}>
    <div className="fg"><label className="fl">Nome</label><input className="fi" value={form.name} onChange={e=>setF({...form,name:e.target.value})}/></div>
    <div className="fg"><label className="fl">Email</label><input className="fi" value={form.email} onChange={e=>setF({...form,email:e.target.value})}/></div>
    {modal==='new'&&<div className="fg"><label className="fl">Senha</label><input className="fi" value={form.password} onChange={e=>setF({...form,password:e.target.value})} placeholder="mudar123"/></div>}
    <div className="fr"><div className="fg"><label className="fl">Função</label><select className="fs" value={form.role} onChange={e=>setF({...form,role:e.target.value})}>{ROLES.map(r=><option key={r.id} value={r.id}>{r.label}</option>)}</select></div>
    <div className="fg"><label className="fl">Status</label><select className="fs" value={form.active?'1':'0'} onChange={e=>setF({...form,active:e.target.value==='1'})}><option value="1">Ativo</option><option value="0">Inativo</option></select></div></div>
    </Modal>}</>;
}

// Clients
function PgClients({clients,setClients,jobs,services,token}){
  const SvcN=({id})=>services.find(s=>s.id===id)?.name||id;
  const[search,setSe]=useState('');const[sort,setSo]=useState({field:'name',dir:'asc'});
  const[modal,setMo]=useState(null);const[viewC,setVC]=useState(null);const[showPw,setSP]=useState(false);
  const empty={name:'',cpf:'',clinic:'',cro:'',email:'',phone:'',city:'',state:'',address:'',neighborhood:'',zip:'',birthDate:'',login:'',password:''};
  const[form,setF]=useState(empty);
  const doSort=f=>setSo(p=>({field:f,dir:p.field===f&&p.dir==='asc'?'desc':'asc'}));
  const filtered=useMemo(()=>{const q=search.toLowerCase();let l=clients.filter(c=>c.name.toLowerCase().includes(q)||(c.clinic||'').toLowerCase().includes(q)||(c.cro||'').toLowerCase().includes(q));l.sort((a,b)=>{const v=sort.dir==='asc'?1:-1;return(a[sort.field]||'').localeCompare(b[sort.field]||'')*v;});return l;},[clients,search,sort]);
  const openNew=()=>{setF(empty);setMo('new');setSP(false);};
  const openEdit=c=>{setF({name:c.name,cpf:c.cpf||'',clinic:c.clinic,cro:c.cro,email:c.email,phone:c.phone,city:c.city,state:c.state||'',address:c.address||'',neighborhood:c.neighborhood||'',zip:c.zip||'',birthDate:c.birthDate||c.birth_date||'',login:c.login||'',password:''});setMo(c);setSP(false);};
  const { addToast } = useToast();
  const save=async()=>{
    try{
      const payload={name:form.name,cpf:form.cpf||null,clinic:form.clinic||null,cro:form.cro||null,email:form.email,phone:form.phone||null,birth_date:form.birthDate||null,address:form.address||null,neighborhood:form.neighborhood||null,city:form.city||null,state:form.state||null,zip:form.zip||null,login:form.login||null,password:form.password||null};
      if(modal==='new'){
        const r=await fetch('/api/clients',{method:'POST',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify(payload)});
        const d=await r.json();if(!r.ok)throw new Error(d.error||'Erro ao cadastrar');
        setClients(p=>[...p,{id:d.id,...payload,birthDate:form.birthDate}]);
        addToast('Cliente cadastrado!','success');
      }else{
        const r=await fetch(`/api/clients/${modal.id}`,{method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify(payload)});
        if(!r.ok)throw new Error('Erro ao atualizar');
        if(form.password){await fetch(`/api/clients/${modal.id}/password`,{method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({password:form.password})});}
        setClients(p=>p.map(c=>c.id===modal.id?{...c,...payload,birthDate:form.birthDate}:c));
        addToast('Cliente atualizado!','success');
      }
      setMo(null);
    }catch(e){addToast(e.message,'error');}
  };
  const cJobs=id=>jobs.filter(j=>j.clientId===id);
  const cRev=id=>cJobs(id).filter(j=>j.paid).reduce((s,j)=>s+j.value,0);
  const cPend=id=>cJobs(id).filter(j=>!j.paid).reduce((s,j)=>s+j.value,0);

  if(viewC){const cj=cJobs(viewC.id);return <>
    <button className="btn bg" onClick={()=>setVC(null)} style={{marginBottom:12}}>{I.back} Voltar</button>
    <div className="cd" style={{marginBottom:12}}><div style={{padding:18}}>
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'flex-start'}}>
        <div><h3 style={{fontSize:16,marginBottom:2}}>{viewC.name}</h3><div style={{fontSize:12,color:'var(--t2)'}}>{viewC.clinic} — {viewC.cro}</div>
        {viewC.cpf&&<div style={{fontSize:11,color:'var(--t3)'}}>CPF: {viewC.cpf}</div>}
        <div style={{fontSize:11,color:'var(--t3)',marginTop:4}}>{viewC.email} · {viewC.phone}</div>
        <div style={{fontSize:11,color:'var(--t3)'}}>{viewC.address}, {viewC.neighborhood} — {viewC.city}/{viewC.state} · CEP: {viewC.zip}</div>
        {viewC.birthDate&&<div style={{fontSize:11,color:'var(--t3)'}}>Nascimento: {fmtD(viewC.birthDate)}</div>}</div>
        <button className="btn bs bsm" onClick={()=>openEdit(viewC)}>{I.edit} Editar</button>
      </div>
      <div className="fsum" style={{marginTop:14}}><div className="fbox"><div className="l">Trabalhos</div><div className="v">{cj.length}</div></div><div className="fbox"><div className="l">Recebido</div><div className="v ok">{fmt(cRev(viewC.id))}</div></div><div className="fbox"><div className="l">Pendente</div><div className="v err">{fmt(cPend(viewC.id))}</div></div></div>
    </div></div>
    <div className="cd"><div className="cd-h"><h3>Trabalhos</h3></div><div className="tb-wrap"><table className="tb"><thead><tr><th>ID</th><th>Serviço</th><th>Data</th><th>Valor</th><th>Status</th><th>Pago</th></tr></thead><tbody>
    {cj.map(j=><tr key={j.id}><td style={{fontFamily:'var(--m)',fontSize:10}}>{j.id}</td><td><SvcN id={j.service}/></td><td>{fmtD(j.date)}</td><td style={{fontFamily:'var(--m)',fontWeight:600}}>{fmt(j.value)}</td><td><StB id={j.status}/></td><td><span className="chip" style={{background:j.paid?'rgba(16,185,129,.1)':'rgba(239,68,68,.1)',color:j.paid?'var(--ok)':'var(--err)'}}>{j.paid?'Pago':'Pend.'}</span></td></tr>)}
    </tbody></table></div></div></>;
  }

  return <><div className="cd"><div className="cd-h"><h3>Clientes ({filtered.length})</h3><div style={{display:'flex',gap:6,flexWrap:'wrap'}}>
    <div className="sbox" style={{width:220}}>{I.search}<input placeholder="Nome, clínica ou CRO..." value={search} onChange={e=>setSe(e.target.value)}/></div>
    <button className="btn bp" onClick={openNew}>{I.plus} Novo Cliente</button></div></div>
    <div className="tb-wrap"><table className="tb"><thead><tr>
      <STh label="Cliente" field="name" sort={sort} onSort={doSort}/><STh label="Clínica" field="clinic" sort={sort} onSort={doSort}/><th>CRO</th><th>CPF</th><th>Cidade</th><th>Trab.</th><th>Receita</th><th>Pend.</th><th>Ações</th>
    </tr></thead><tbody>{filtered.map(c=><tr key={c.id}>
      <td><div style={{color:'var(--t1)',fontWeight:500}}>{c.name}</div><div style={{fontSize:9,color:'var(--t3)'}}>{c.email}</div></td>
      <td>{c.clinic}</td><td style={{fontFamily:'var(--m)',fontSize:10}}>{c.cro}</td>
      <td style={{fontFamily:'var(--m)',fontSize:10}}>{c.cpf||'—'}</td>
      <td>{c.city}</td>
      <td style={{fontFamily:'var(--m)'}}>{cJobs(c.id).length}</td>
      <td style={{fontFamily:'var(--m)',color:'var(--ok)',fontWeight:600}}>{fmt(cRev(c.id))}</td>
      <td style={{fontFamily:'var(--m)',color:cPend(c.id)>0?'var(--wn)':'var(--t3)',fontWeight:600}}>{fmt(cPend(c.id))}</td>
      <td><div style={{display:'flex',gap:4}}><button className="btn bg bsm" onClick={()=>setVC(c)}>{I.eye}</button><button className="btn bg bsm" onClick={()=>openEdit(c)}>{I.edit}</button></div></td>
    </tr>)}</tbody></table></div></div>
    {modal&&<Modal title={modal==='new'?'Novo Cliente':'Editar Cliente'} large onClose={()=>setMo(null)} footer={<><button className="btn bs" onClick={()=>setMo(null)}>Cancelar</button><button className="btn bp" onClick={save}>Salvar</button></>}>
      <div className="fr"><div className="fg"><label className="fl">Nome Completo</label><input className="fi" value={form.name} onChange={e=>setF({...form,name:e.target.value})}/></div>
      <div className="fg"><label className="fl">CPF</label><input className="fi" value={form.cpf} onChange={e=>setF({...form,cpf:cpfMask(e.target.value)})} placeholder="000.000.000-00"/></div></div>
      <div className="fr"><div className="fg"><label className="fl">Data de Nascimento</label><input className="fi" type="date" value={form.birthDate} onChange={e=>setF({...form,birthDate:e.target.value})}/></div>
      <div className="fg"><label className="fl">CRO</label><input className="fi" value={form.cro} onChange={e=>setF({...form,cro:e.target.value})} placeholder="CRO-UF 00000"/></div></div>
      <div className="fr"><div className="fg"><label className="fl">Clínica</label><input className="fi" value={form.clinic} onChange={e=>setF({...form,clinic:e.target.value})}/></div>
      <div className="fg"><label className="fl">Email</label><input className="fi" value={form.email} onChange={e=>setF({...form,email:e.target.value})}/></div></div>
      <div className="fg"><label className="fl">Telefone</label><input className="fi" value={form.phone} onChange={e=>setF({...form,phone:e.target.value})}/></div>
      <div className="fg"><label className="fl">Endereço</label><input className="fi" value={form.address} onChange={e=>setF({...form,address:e.target.value})} placeholder="Rua, número, complemento"/></div>
      <div className="fr3"><div className="fg"><label className="fl">Bairro</label><input className="fi" value={form.neighborhood} onChange={e=>setF({...form,neighborhood:e.target.value})}/></div>
      <div className="fg"><label className="fl">Cidade</label><input className="fi" value={form.city} onChange={e=>setF({...form,city:e.target.value})}/></div>
      <div className="fg"><label className="fl">Estado</label><input className="fi" value={form.state} onChange={e=>setF({...form,state:e.target.value})} placeholder="UF"/></div></div>
      <div className="fg"><label className="fl">CEP</label><input className="fi" style={{width:160}} value={form.zip} onChange={e=>setF({...form,zip:e.target.value})} placeholder="00000-000"/></div>
      <div style={{borderTop:'1px solid var(--bd)',marginTop:10,paddingTop:14}}>
        <div style={{fontSize:12,fontWeight:600,marginBottom:10,color:'var(--t2)',display:'flex',alignItems:'center',gap:6}}>{I.lock} Acesso ao Portal</div>
        <div className="fr"><div className="fg"><label className="fl">Login</label><input className="fi" value={form.login} onChange={e=>setF({...form,login:e.target.value})} placeholder="usuario.login"/></div>
        <div className="fg"><label className="fl">Senha</label><div className="pwd-wrap"><input className="fi" type={showPw?'text':'password'} value={form.password} onChange={e=>setF({...form,password:e.target.value})} placeholder="••••••••"/><span className="pwd-toggle" onClick={()=>setSP(!showPw)}>{showPw?I.eyeOff:I.eye}</span></div></div></div>
      </div>
    </Modal>}</>;
}

// ═══ 3D SCAN VIEWER ═══
function ScanViewer3D({url, name}) {
  const mountRef = useRef(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!url || !mountRef.current) return;
    const mount = mountRef.current;
    setLoading(true);
    setError(null);

    const ext = name.split('.').pop().toLowerCase();
    if (!['stl','obj','ply'].includes(ext)) {
      setLoading(false);
      setError('Formato não suportado para visualização 3D');
      return;
    }

    const w = mount.clientWidth || 400;
    const h = mount.clientHeight || 340;

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x0d1117);

    const camera = new THREE.PerspectiveCamera(45, w / h, 0.01, 100000);
    camera.position.set(0, 0, 200);

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(w, h);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.shadowMap.enabled = true;
    mount.appendChild(renderer.domElement);

    const ambLight = new THREE.AmbientLight(0xffffff, 0.45);
    scene.add(ambLight);
    const dirLight1 = new THREE.DirectionalLight(0xffffff, 0.9);
    dirLight1.position.set(1, 2, 3);
    scene.add(dirLight1);
    const dirLight2 = new THREE.DirectionalLight(0x6699ff, 0.3);
    dirLight2.position.set(-2, -1, -2);
    scene.add(dirLight2);

    const controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.08;
    controls.autoRotate = false;

    const fitCamera = (object) => {
      const box = new THREE.Box3().setFromObject(object);
      const center = box.getCenter(new THREE.Vector3());
      const size = box.getSize(new THREE.Vector3());
      object.position.sub(center);
      const maxDim = Math.max(size.x, size.y, size.z);
      camera.position.set(0, 0, maxDim * 1.8);
      camera.near = maxDim * 0.001;
      camera.far = maxDim * 20;
      camera.updateProjectionMatrix();
      controls.target.set(0, 0, 0);
      controls.update();
    };

    const material = new THREE.MeshPhongMaterial({
      color: 0x0ea5e9,
      specular: 0x334155,
      shininess: 60,
      side: THREE.DoubleSide
    });

    let animId;
    const animate = () => { animId = requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); };
    animate();

    if (ext === 'stl') {
      const loader = new THREE.STLLoader();
      loader.load(url, (geo) => {
        geo.computeVertexNormals();
        const mesh = new THREE.Mesh(geo, material);
        scene.add(mesh);
        fitCamera(mesh);
        setLoading(false);
      }, undefined, () => { setError('Erro ao carregar STL'); setLoading(false); });
    } else if (ext === 'ply') {
      const loader = new THREE.PLYLoader();
      loader.load(url, (geo) => {
        geo.computeVertexNormals();
        const mesh = new THREE.Mesh(geo, material);
        scene.add(mesh);
        fitCamera(mesh);
        setLoading(false);
      }, undefined, () => { setError('Erro ao carregar PLY'); setLoading(false); });
    } else if (ext === 'obj') {
      const loader = new THREE.OBJLoader();
      loader.load(url, (obj) => {
        obj.traverse(c => { if (c.isMesh) c.material = material; });
        scene.add(obj);
        fitCamera(obj);
        setLoading(false);
      }, undefined, () => { setError('Erro ao carregar OBJ'); setLoading(false); });
    }

    const handleResize = () => {
      const nw = mount.clientWidth; const nh = mount.clientHeight;
      camera.aspect = nw / nh; camera.updateProjectionMatrix(); renderer.setSize(nw, nh);
    };
    window.addEventListener('resize', handleResize);

    return () => {
      cancelAnimationFrame(animId);
      window.removeEventListener('resize', handleResize);
      controls.dispose();
      renderer.dispose();
      if (mount.contains(renderer.domElement)) mount.removeChild(renderer.domElement);
    };
  }, [url, name]);

  return (
    <div style={{width:'100%',height:'100%',position:'relative'}}>
      <div ref={mountRef} style={{width:'100%',height:'100%'}}/>
      {loading&&<div style={{position:'absolute',inset:0,display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',color:'var(--t3)',gap:8,background:'rgba(13,17,23,0.7)',pointerEvents:'none'}}>
        <div style={{width:28,height:28,border:'3px solid var(--bd)',borderTopColor:'var(--ac)',borderRadius:'50%',animation:'spin .8s linear infinite'}}/>
        <div style={{fontSize:11}}>Carregando modelo 3D...</div>
      </div>}
      {error&&<div style={{position:'absolute',inset:0,display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',color:'var(--t3)',gap:6}}>
        <div style={{fontSize:20}}>{I.cube}</div>
        <div style={{fontSize:12}}>{error}</div>
      </div>}
      {!loading&&!error&&<div style={{position:'absolute',bottom:8,left:0,right:0,textAlign:'center',fontSize:10,color:'rgba(148,163,184,0.5)',pointerEvents:'none'}}>Arraste para rotacionar · Scroll para zoom</div>}
    </div>
  );
}

// Jobs
function PgJobs({jobs,setJobs,clients,users,services,token}){
  const SvcN=({id})=>services.find(s=>s.id===id)?.name||id;
  const SVM=Object.fromEntries(services.map(s=>[s.id,s]));
  const[sort,setSo]=useState({field:'date',dir:'desc'});const[fSt,setFS]=useState('all');const[fPd,setFP]=useState('all');
  const[modal,setMo]=useState(null);const[jobTab,setJT]=useState('info');const[preview,setPrev]=useState(null);
  const techs=users.filter(u=>u.active&&(u.role==='tecnico'||u.role==='admin'));
  const cN=id=>clients.find(c=>c.id===id)?.name||'—';const uN=id=>users.find(u=>u.id===id)?.name||'—';
  const doSort=f=>setSo(p=>({field:f,dir:p.field===f&&p.dir==='asc'?'desc':'asc'}));
  const empty={clientId:clients[0]?.id||'',service:services[0]?.id||'',notes:'',assignedTo:'',value:''};
  const[form,setF]=useState(empty);
  const[fileForm,setFF]=useState({photos:[],scans:{sup:null,inf:null,ext_sup:null,ext_inf:null}});

  const filtered=useMemo(()=>{let l=[...jobs];
    if(fSt!=='all')l=l.filter(j=>j.status===fSt);
    if(fPd==='paid')l=l.filter(j=>j.paid);if(fPd==='pending')l=l.filter(j=>!j.paid);
    l.sort((a,b)=>{const d=sort.dir==='asc'?1:-1;if(sort.field==='date')return a.date<b.date?-d:d;if(sort.field==='client')return cN(a.clientId)<cN(b.clientId)?-d:d;if(sort.field==='status')return STATUS_FLOW.findIndex(s=>s.id===a.status)<STATUS_FLOW.findIndex(s=>s.id===b.status)?-d:d;if(sort.field==='value')return(a.value-b.value)*d;return 0;});
    return l;},[jobs,fSt,fPd,sort,clients]);

  const openNew=()=>{setF(empty);setFF({photos:[],scans:{sup:null,inf:null,ext_sup:null,ext_inf:null}});setMo('new');setJT('info');};
  const openEdit=j=>{setF({clientId:j.clientId,service:j.service,notes:j.notes,assignedTo:j.assignedTo||'',value:j.value});setFF(j.files||{photos:[],scans:{sup:null,inf:null,ext_sup:null,ext_inf:null}});setMo({edit:j});setJT('info');};
  const { addToast } = useToast();
  const saveNew=async ()=>{
    const svc=SVM[form.service];
    const newJ = { client_id: Number(form.clientId), service_id: form.service, value: Number(form.value)||svc?.base_price||0, notes: form.notes, assigned_to: form.assignedTo?Number(form.assignedTo):null, origin:'admin' };
    try {
      const res = await fetch('/api/jobs', { method:'POST', headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`}, body:JSON.stringify(newJ) });
      if(!res.ok) throw new Error('Falha ao salvar no BD');
      const d = await res.json();
      setJobs(p=>[{ id: d.id, clientId: newJ.client_id, service: newJ.service_id, value: newJ.value, notes: newJ.notes, assignedTo: newJ.assigned_to, origin: newJ.origin, status: 'recebido', date: new Date().toISOString().split('T')[0], paid: false }, ...p]);
      setMo(null); addToast('Novo trabalho salvo!', 'success');
    } catch(e) { addToast(e.message, 'error'); }
  };
  const saveEdit=async()=>{
  const svc=SVM[form.service];
  const newJ = { client_id: Number(form.clientId), service_id: form.service, value: Number(form.value)||svc?.base_price||0, notes: form.notes, assigned_to: form.assignedTo?Number(form.assignedTo):null };
  try {
    const res = await fetch(`/api/jobs/${modal.edit.id}`, { method:'PUT', headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`}, body:JSON.stringify(newJ) });
    if(!res.ok) throw new Error('Falha ao atualizar');
    setJobs(p=>p.map(j=>j.id===modal.edit.id?{...j,clientId:Number(form.clientId),service:form.service,notes:form.notes,assignedTo:form.assignedTo?Number(form.assignedTo):j.assignedTo,value:Number(form.value)||j.value,files:fileForm}:j));
    setMo(null); addToast('Trabalho atualizado!', 'success');
  } catch(e) { addToast(e.message, 'error'); }
};
  const advance=async j=>{const i=STATUS_FLOW.findIndex(s=>s.id===j.status);if(i<STATUS_FLOW.length-1){const ns=STATUS_FLOW[i+1].id;try{await fetch(`/api/jobs/${j.id}/status`, {method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({status:ns})});setJobs(p=>p.map(x=>x.id===j.id?{...x,status:ns}:x));setMo({...j,status:ns});addToast(`Status: ${STATUS_FLOW[i+1].label}`,'info');}catch(e){addToast('Erro API','error');}}};

  const handlePhotoAdd=()=>{const name=`foto_${Date.now()}.jpg`;setFF(p=>({...p,photos:[...p.photos,{name,url:'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><rect fill="%23334155" width="200" height="200"/><text x="50%" y="50%" fill="%2394A3B8" text-anchor="middle" dy=".3em" font-size="14">Foto</text></svg>'}]}));};
  const handleScanAdd=(slot)=>{const name=`scan_${slot}_${Date.now()}.stl`;setFF(p=>({...p,scans:{...p.scans,[slot]:{name,url:null}}}));};
  const handleScanRemove=(slot)=>setFF(p=>({...p,scans:{...p.scans,[slot]:null}}));
  const handlePhotoRemove=(i)=>setFF(p=>({...p,photos:p.photos.filter((_,idx)=>idx!==i)}));

  const isView=modal&&typeof modal==='object'&&!modal.edit;
  const isEdit=modal&&typeof modal==='object'&&modal.edit;
  const formModal=modal==='new'||isEdit;

  return <>
    <div className="fbar">
      <span className="lbl">Status:</span><button className={`btn ${fSt==='all'?'bp':'bs'} bsm`} onClick={()=>setFS('all')}>Todos</button>
      {STATUS_FLOW.map(s=><button key={s.id} className={`btn ${fSt===s.id?'bp':'bs'} bsm`} onClick={()=>setFS(s.id)}>{s.label} ({jobs.filter(j=>j.status===s.id).length})</button>)}
      <span className="lbl" style={{marginLeft:10}}>Pgto:</span>
      <button className={`btn ${fPd==='all'?'bp':'bs'} bsm`} onClick={()=>setFP('all')}>Todos</button>
      <button className={`btn ${fPd==='paid'?'bp':'bs'} bsm`} onClick={()=>setFP('paid')}>Pagos</button>
      <button className={`btn ${fPd==='pending'?'bp':'bs'} bsm`} onClick={()=>setFP('pending')}>Pendentes</button>
    </div>
    <div className="cd"><div className="cd-h"><h3>Trabalhos ({filtered.length})</h3><button className="btn bp" onClick={openNew}>{I.plus} Novo Trabalho</button></div>
    <div className="tb-wrap"><table className="tb"><thead><tr><th>ID</th><STh label="Cliente" field="client" sort={sort} onSort={doSort}/><th>Serviço</th><STh label="Data" field="date" sort={sort} onSort={doSort}/><STh label="Valor" field="value" sort={sort} onSort={doSort}/><STh label="Status" field="status" sort={sort} onSort={doSort}/><th>Pago</th><th>Atribuído</th><th>Ações</th></tr></thead><tbody>
    {filtered.map(j=><tr key={j.id}><td style={{fontFamily:'var(--m)',fontSize:10}}>{j.id}</td><td style={{color:'var(--t1)',fontWeight:500}}>{cN(j.clientId)}</td><td><SvcN id={j.service}/></td><td>{fmtD(j.date)}</td><td style={{fontFamily:'var(--m)',fontWeight:600}}>{fmt(j.value)}</td><td><StB id={j.status}/></td><td><span className="chip" style={{background:j.paid?'rgba(16,185,129,.1)':'rgba(239,68,68,.1)',color:j.paid?'var(--ok)':'var(--err)'}}>{j.paid?'Pago':'Pend.'}</span></td><td style={{fontSize:11,color:j.assignedTo?'var(--pu)':'var(--err)'}}>{j.assignedTo?uN(j.assignedTo):'Não atrib.'}</td><td><div style={{display:'flex',gap:4}}><button className="btn bg bsm" onClick={()=>{setMo(j);setJT('info');}}>{I.eye}</button><button className="btn bg bsm" onClick={()=>openEdit(j)}>{I.edit}</button></div></td></tr>)}
    </tbody></table></div></div>

    {/* View */}
    {isView&&<Modal title={`Trabalho ${modal.id}`} large onClose={()=>setMo(null)} footer={<><button className="btn bs" onClick={()=>setMo(null)}>Fechar</button>{modal.status!=='entregue'&&<button className="btn bp" onClick={()=>advance(modal)}>Avançar Status</button>}</>}>
      <div className="tabs"><div className={`tab ${jobTab==='info'?'on':''}`} onClick={()=>setJT('info')}>Informações</div><div className={`tab ${jobTab==='files'?'on':''}`} onClick={()=>setJT('files')}>Arquivos</div></div>
      {jobTab==='info'&&<><div style={{marginBottom:12}}><div style={{fontSize:10,color:'var(--t3)'}}>Cliente</div><div style={{fontWeight:600,fontSize:15}}>{cN(modal.clientId)}</div></div>
      <div className="fr" style={{marginBottom:12}}><div><div style={{fontSize:10,color:'var(--t3)'}}>Serviço</div><div style={{fontWeight:500}}><SvcN id={modal.service}/></div></div><div><div style={{fontSize:10,color:'var(--t3)'}}>Valor</div><div style={{fontFamily:'var(--m)',fontWeight:700,fontSize:16,color:'var(--ac)'}}>{fmt(modal.value)}</div></div></div>
      <div className="fr" style={{marginBottom:12}}><div><div style={{fontSize:10,color:'var(--t3)'}}>Atribuído</div><div style={{fontWeight:500,color:modal.assignedTo?'var(--pu)':'var(--err)'}}>{modal.assignedTo?uN(modal.assignedTo):'Não atribuído'}</div></div><div><div style={{fontSize:10,color:'var(--t3)'}}>Origem</div><div style={{fontWeight:500}}>{modal.origin==='client'?'Portal Cliente':'Admin'}</div></div></div>
      {modal.paid&&<div className="fr" style={{marginBottom:12}}><div><div style={{fontSize:10,color:'var(--t3)'}}>Pgto</div><div style={{fontWeight:500,color:'var(--ok)'}}>{modal.payMethod||'—'}</div></div><div><div style={{fontSize:10,color:'var(--t3)'}}>Data Pgto</div><div>{fmtD(modal.paidDate)}</div></div></div>}
      <div style={{fontSize:10,color:'var(--t3)'}}>Obs.</div><div style={{fontSize:12,color:'var(--t2)',marginBottom:14}}>{modal.notes||'—'}</div>
      <FlowT status={modal.status}/></>}
      {jobTab==='files'&&<>
        <div style={{fontSize:12,fontWeight:600,marginBottom:8,color:'var(--t2)'}}>Escaneamentos</div>
        {SCAN_SLOTS.map(sl=>{const f=modal.files?.scans?.[sl.id];return <div className="file-slot" key={sl.id}><div className="fs-label">{sl.label}</div>{f?<><div className="scan-mini-viewer"><div className="smv-icon">{I.cube}</div><div className="smv-ext">{f.name.split('.').pop().toUpperCase()}</div></div><div className="fs-name" style={{flex:1}}>{f.name}</div><button className="btn bg bsm" onClick={()=>setPrev({type:'scan',name:f.name,url:f.url||null})}>{I.eye}</button></>:<span style={{fontSize:11,color:'var(--t3)'}}>Nenhum arquivo</span>}</div>;})}
        <div style={{fontSize:12,fontWeight:600,marginBottom:8,marginTop:14,color:'var(--t2)'}}>Fotos ({modal.files?.photos?.length||0})</div>
        {modal.files?.photos?.length>0?<div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(100px,1fr))',gap:8}}>
          {modal.files.photos.map((p,i)=><div key={i} style={{background:'var(--bg3)',border:'1px solid var(--bd)',borderRadius:'var(--rs)',padding:6,cursor:'pointer',textAlign:'center'}} onClick={()=>setPrev({type:'photo',url:p.url,name:p.name})}><div style={{height:60,display:'flex',alignItems:'center',justifyContent:'center',marginBottom:4}}>{p.url?<img src={p.url} alt="" style={{maxHeight:60,maxWidth:'100%',borderRadius:4}}/>:<span style={{color:'var(--t3)'}}>{I.img}</span>}</div><div style={{fontSize:9,color:'var(--t3)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{p.name}</div></div>)}
        </div>:<div style={{fontSize:11,color:'var(--t3)'}}>Nenhuma foto</div>}
        {preview&&<div className="preview-area" style={{marginTop:14,minHeight:preview.type==='scan'?360:200}}><button className="ibtn" style={{position:'absolute',top:8,right:8,zIndex:2}} onClick={()=>setPrev(null)}>{I.close}</button>{preview.type==='scan'&&preview.url?<ScanViewer3D url={preview.url} name={preview.name}/>:preview.type==='photo'&&preview.url?<img src={preview.url} alt=""/>:<div className="ph"><div style={{marginBottom:6}}>{I.cube}</div><div>{preview.name}</div><div style={{fontSize:10,marginTop:4}}>Visualização de escaneamento 3D não disponível</div></div>}</div>}
      </>}
    </Modal>}

    {/* New/Edit */}
    {formModal&&<Modal title={modal==='new'?'Novo Trabalho':'Editar Trabalho'} large onClose={()=>setMo(null)} footer={<><button className="btn bs" onClick={()=>setMo(null)}>Cancelar</button><button className="btn bp" onClick={modal==='new'?saveNew:saveEdit}>Salvar</button></>}>
      <div className="tabs"><div className={`tab ${jobTab==='info'?'on':''}`} onClick={()=>setJT('info')}>Informações</div><div className={`tab ${jobTab==='files'?'on':''}`} onClick={()=>setJT('files')}>Arquivos</div></div>
      {jobTab==='info'&&<><div className="fr"><div className="fg"><label className="fl">Cliente</label><select className="fs" value={form.clientId} onChange={e=>setF({...form,clientId:e.target.value})}>{clients.map(c=><option key={c.id} value={c.id}>{c.name} — {c.clinic}</option>)}</select></div>
      <div className="fg"><label className="fl">Serviço</label><select className="fs" value={form.service} onChange={e=>setF({...form,service:e.target.value,value:SVM[e.target.value]?.base_price||form.value})}>{services.map(s=><option key={s.id} value={s.id}>{s.name}</option>)}</select></div></div>
      <div className="fr"><div className="fg"><label className="fl">Valor (R$)</label><input className="fi" type="number" value={form.value} onChange={e=>setF({...form,value:e.target.value})}/></div>
      <div className="fg"><label className="fl">Atribuir Funcionário</label><select className="fs" value={form.assignedTo} onChange={e=>setF({...form,assignedTo:e.target.value})}><option value="">— Nenhum (direcionar depois) —</option>{techs.map(u=><option key={u.id} value={u.id}>{u.name} ({RM[u.role]?.label})</option>)}</select></div></div>
      <div className="fg"><label className="fl">Observações</label><textarea className="fta" rows={2} value={form.notes} onChange={e=>setF({...form,notes:e.target.value})}/></div></>}
      {jobTab==='files'&&<>
        <div style={{fontSize:12,fontWeight:600,marginBottom:8,color:'var(--t2)'}}>Escaneamentos (até 4)</div>
        {SCAN_SLOTS.map(sl=><div className="file-slot" key={sl.id}><div className="fs-label">{sl.label}</div>{fileForm.scans[sl.id]?<><div className="fs-name">{fileForm.scans[sl.id].name}</div><button className="btn berr bsm" onClick={()=>handleScanRemove(sl.id)}>{I.trash}</button></>:<button className="btn bs bsm" onClick={()=>handleScanAdd(sl.id)}>{I.plus} Carregar</button>}</div>)}
        <div style={{fontSize:12,fontWeight:600,marginBottom:8,marginTop:14,color:'var(--t2)'}}>Fotos</div>
        {fileForm.photos.length>0&&<div style={{display:'flex',gap:6,flexWrap:'wrap',marginBottom:8}}>
          {fileForm.photos.map((p,i)=><div key={i} style={{background:'var(--bg3)',border:'1px solid var(--bd)',borderRadius:6,padding:6,display:'flex',alignItems:'center',gap:6}}><span style={{fontSize:10,color:'var(--t2)'}}>{p.name}</span><button className="btn berr bsm" onClick={()=>handlePhotoRemove(i)}>{I.trash}</button></div>)}
        </div>}
        <button className="btn bs" onClick={handlePhotoAdd}>{I.cam} Adicionar Foto</button>
      </>}
    </Modal>}
  </>;
}

// Finance Overview
function PgFinOv({jobs,clients}){
  const paid=jobs.filter(j=>j.paid).reduce((s,j)=>s+j.value,0);const pend=jobs.filter(j=>!j.paid).reduce((s,j)=>s+j.value,0);
  const byC=clients.map(c=>{const cj=jobs.filter(j=>j.clientId===c.id);return{...c,paid:cj.filter(j=>j.paid).reduce((s,j)=>s+j.value,0),pend:cj.filter(j=>!j.paid).reduce((s,j)=>s+j.value,0),count:cj.length};}).sort((a,b)=>(b.paid+b.pend)-(a.paid+a.pend));
  return <><div className="fsum"><div className="fbox"><div className="l">Total Faturado</div><div className="v wn">{fmt(paid+pend)}</div></div><div className="fbox"><div className="l">Recebido</div><div className="v ok">{fmt(paid)}</div></div><div className="fbox"><div className="l">Pendente</div><div className="v err">{fmt(pend)}</div></div></div>
  <div className="cd"><div className="cd-h"><h3>Financeiro por Cliente</h3><button className="btn bs bsm">{I.dl} Exportar</button></div><div className="tb-wrap"><table className="tb"><thead><tr><th>Cliente</th><th>Clínica</th><th>Trab.</th><th>Recebido</th><th>Pendente</th><th>Total</th></tr></thead><tbody>
  {byC.map(c=><tr key={c.id}><td style={{color:'var(--t1)',fontWeight:500}}>{c.name}</td><td>{c.clinic}</td><td style={{fontFamily:'var(--m)'}}>{c.count}</td><td style={{fontFamily:'var(--m)',color:'var(--ok)',fontWeight:600}}>{fmt(c.paid)}</td><td style={{fontFamily:'var(--m)',color:c.pend>0?'var(--err)':'var(--t3)',fontWeight:600}}>{fmt(c.pend)}</td><td style={{fontFamily:'var(--m)',fontWeight:700}}>{fmt(c.paid+c.pend)}</td></tr>)}
  </tbody></table></div></div></>;
}

// Finance Payments
function PgFinPay({jobs,setJobs,clients,services,token}){
  const SvcN=({id})=>services.find(s=>s.id===id)?.name||id;
  const SVM=Object.fromEntries(services.map(s=>[s.id,s]));
  const unpaid=jobs.filter(j=>!j.paid);const cN=id=>clients.find(c=>c.id===id)?.name||'—';
  const[confirm,setConf]=useState(null);
  const { addToast } = useToast();
  const doConfirm=async (method)=>{try{await fetch(`/api/jobs/${confirm.id}/pay`,{method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({pay_method:method})});setJobs(p=>p.map(j=>j.id===confirm.id?{...j,paid:true,paidDate:new Date().toISOString().split('T')[0],payMethod:method}:j));setConf(null);addToast('Pagamento baixado!','success');}catch(e){addToast('Erro API','error');}};
  return <><div className="fsum"><div className="fbox"><div className="l">Total Pendente</div><div className="v err">{fmt(unpaid.reduce((s,j)=>s+j.value,0))}</div></div><div className="fbox"><div className="l">Trabalhos Pendentes</div><div className="v wn">{unpaid.length}</div></div><div className="fbox"><div className="l">Clientes c/ Pendência</div><div className="v">{new Set(unpaid.map(j=>j.clientId)).size}</div></div></div>
  <div className="cd"><div className="cd-h"><h3>Pendentes de Pagamento</h3></div><div className="tb-wrap"><table className="tb"><thead><tr><th>ID</th><th>Cliente</th><th>Serviço</th><th>Data</th><th>Valor</th><th>Status</th><th>Ação</th></tr></thead><tbody>
  {unpaid.length===0?<tr><td colSpan={7}><div className="empty"><h4>Nenhum pendente</h4></div></td></tr>:unpaid.map(j=><tr key={j.id}><td style={{fontFamily:'var(--m)',fontSize:10}}>{j.id}</td><td style={{color:'var(--t1)',fontWeight:500}}>{cN(j.clientId)}</td><td><SvcN id={j.service}/></td><td>{fmtD(j.date)}</td><td style={{fontFamily:'var(--m)',fontWeight:700}}>{fmt(j.value)}</td><td><StB id={j.status}/></td><td><button className="btn bok bsm" onClick={()=>setConf(j)}>{I.check} Dar Baixa</button></td></tr>)}
  </tbody></table></div></div>
  {confirm&&<PayConfirm title={`${confirm.id} — ${cN(confirm.clientId)} — ${SVM[confirm.service]?.name}`} amount={confirm.value} onConfirm={doConfirm} onCancel={()=>setConf(null)}/>}
  </>;
}

// Bills
function PgBills({bills,setBills,token}){
  const[fSt,setFS]=useState('all');const[fCat,setFC]=useState('all');const[modal,setMo]=useState(null);const[confirm,setConf]=useState(null);
  const empty={description:'',supplier:'',category:'material',value:'',dueDate:'',recurring:false,notes:''};const[form,setF]=useState(empty);
  const today=new Date().toISOString().split('T')[0];
  const proc=bills.map(b=>({...b,status:b.status!=='pago'&&b.dueDate<today?'vencido':b.status}));
  const filt=proc.filter(b=>fSt==='all'||b.status===fSt).filter(b=>fCat==='all'||b.category===fCat).sort((a,b)=>a.dueDate.localeCompare(b.dueDate));
  const tPd=proc.filter(b=>b.status==='pago').reduce((s,b)=>s+b.value,0);const tPn=proc.filter(b=>b.status==='pendente').reduce((s,b)=>s+b.value,0);const tOv=proc.filter(b=>b.status==='vencido').reduce((s,b)=>s+b.value,0);
  const stC={pago:'var(--ok)',pendente:'var(--wn)',vencido:'var(--err)'};const stL={pago:'Pago',pendente:'Pendente',vencido:'Vencido'};
  const {addToast}=useToast();
  const doConfirm=async(method)=>{try{const r=await fetch(`/api/bills/${confirm.id}/pay`,{method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({pay_method:method})});if(!r.ok)throw new Error('Erro ao pagar');setBills(p=>p.map(b=>b.id===confirm.id?{...b,status:'pago',paidDate:today,payMethod:method}:b));setConf(null);addToast('Pagamento registrado!','success');}catch(e){addToast(e.message,'error');}};
  const saveNew=async()=>{try{const payload={description:form.description,supplier:form.supplier||null,category:form.category,value:parseFloat(form.value)||0,due_date:form.dueDate,recurring:form.recurring?1:0,notes:form.notes||null};const r=await fetch('/api/bills',{method:'POST',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify(payload)});const d=await r.json();if(!r.ok)throw new Error(d.error||'Erro ao salvar');setBills(p=>[...p,{id:d.id,...form,value:parseFloat(form.value)||0,paidDate:null,payMethod:null,status:'pendente'}]);setMo(null);setF(empty);addToast('Conta criada!','success');}catch(e){addToast(e.message,'error');}};
  return <>
    <div className="stats" style={{gridTemplateColumns:'repeat(4,1fr)'}}><div className="st"><div className="sv" style={{fontSize:18,color:'var(--ok)'}}>{fmt(tPd)}</div><div className="sl">Pago</div></div><div className="st"><div className="sv" style={{fontSize:18,color:'var(--wn)'}}>{fmt(tPn)}</div><div className="sl">Pendente</div></div><div className="st"><div className="sv" style={{fontSize:18,color:'var(--err)'}}>{fmt(tOv)}</div><div className="sl">Vencido</div></div><div className="st"><div className="sv" style={{fontSize:18,color:'var(--pu)'}}>{fmt(proc.filter(b=>b.recurring).reduce((s,b)=>s+b.value,0))}</div><div className="sl">Recorrente</div></div></div>
    <div className="fbar"><span className="lbl">Status:</span>{[['all','Todos'],['pendente','Pend.'],['vencido','Venc.'],['pago','Pagos']].map(([k,l])=><button key={k} className={`btn ${fSt===k?'bp':'bs'} bsm`} onClick={()=>setFS(k)}>{l}</button>)}<span className="lbl" style={{marginLeft:8}}>Cat:</span><select className="fs" style={{width:140,padding:'4px 8px',fontSize:11}} value={fCat} onChange={e=>setFC(e.target.value)}><option value="all">Todas</option>{BILL_CATS.map(c=><option key={c.id} value={c.id}>{c.label}</option>)}</select></div>
    <div className="cd"><div className="cd-h"><h3>Contas a Pagar ({filt.length})</h3><button className="btn bp" onClick={()=>{setF(empty);setMo('new');}}>{I.plus} Nova Conta</button></div>
    <div className="tb-wrap"><table className="tb"><thead><tr><th>Descrição</th><th>Fornecedor</th><th>Cat.</th><th>Venc.</th><th>Valor</th><th>Status</th><th></th><th>Ação</th></tr></thead><tbody>
    {filt.map(b=>{const cat=BCM[b.category]||{};return <tr key={b.id}><td style={{color:'var(--t1)',fontWeight:500,maxWidth:200,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{b.description}</td><td>{b.supplier}</td><td><span className="chip" style={{background:`${cat.color}15`,color:cat.color}}>{cat.label}</span></td><td style={{fontFamily:'var(--m)',fontSize:10,color:b.status==='vencido'?'var(--err)':'var(--t2)',fontWeight:b.status==='vencido'?600:400}}>{fmtD(b.dueDate)}</td><td style={{fontFamily:'var(--m)',fontWeight:700}}>{fmt(b.value)}</td><td><span className="badge" style={{background:`${stC[b.status]}15`,color:stC[b.status]}}><span className="dt" style={{background:stC[b.status]}}/>{stL[b.status]}</span></td><td>{b.recurring&&<span style={{color:'var(--pu)'}}>{I.repeat}</span>}</td><td>{b.status!=='pago'&&<button className="btn bok bsm" onClick={()=>setConf(b)}>{I.check} Pagar</button>}</td></tr>;})}
    </tbody></table></div></div>
    {modal==='new'&&<Modal title="Nova Conta" onClose={()=>setMo(null)} footer={<><button className="btn bs" onClick={()=>setMo(null)}>Cancelar</button><button className="btn bp" onClick={saveNew}>Salvar</button></>}>
    <div className="fg"><label className="fl">Descrição</label><input className="fi" value={form.description} onChange={e=>setF({...form,description:e.target.value})}/></div>
    <div className="fr"><div className="fg"><label className="fl">Fornecedor</label><input className="fi" value={form.supplier} onChange={e=>setF({...form,supplier:e.target.value})}/></div><div className="fg"><label className="fl">Categoria</label><select className="fs" value={form.category} onChange={e=>setF({...form,category:e.target.value})}>{BILL_CATS.map(c=><option key={c.id} value={c.id}>{c.label}</option>)}</select></div></div>
    <div className="fr"><div className="fg"><label className="fl">Valor</label><input className="fi" type="number" value={form.value} onChange={e=>setF({...form,value:e.target.value})}/></div><div className="fg"><label className="fl">Vencimento</label><input className="fi" type="date" value={form.dueDate} onChange={e=>setF({...form,dueDate:e.target.value})}/></div></div>
    <div className="fg"><label className="fl">Obs.</label><textarea className="fta" rows={2} value={form.notes} onChange={e=>setF({...form,notes:e.target.value})}/></div>
    <div className="tgl" style={{borderBottom:'none'}}><div><div style={{fontSize:12,fontWeight:500}}>Recorrente</div><div style={{fontSize:10,color:'var(--t3)'}}>Repete mensalmente</div></div><div className={`tgl-s ${form.recurring?'on':''}`} onClick={()=>setF({...form,recurring:!form.recurring})}/></div>
    </Modal>}
    {confirm&&<PayConfirm title={confirm.description} amount={confirm.value} onConfirm={doConfirm} onCancel={()=>setConf(null)}/>}
  </>;
}

// Cash Flow
function PgCashFlow({jobs,bills,services}){
  const SVM=Object.fromEntries(services.map(s=>[s.id,s]));
  const[type,setTy]=useState('all');const[range,setRa]=useState('month');const[customFrom,setCF]=useState('');const[customTo,setCT]=useState('');
  const today=new Date();const todayS=today.toISOString().split('T')[0];
  const getStart=()=>{if(range==='custom')return customFrom||'2020-01-01';const d=new Date(today);if(range==='day')return todayS;if(range==='week'){d.setDate(d.getDate()-d.getDay());return d.toISOString().split('T')[0];}if(range==='month'){return`${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-01`;}if(range==='year')return`${today.getFullYear()}-01-01`;return'2020-01-01';};
  const getEnd=()=>range==='custom'?(customTo||todayS):todayS;
  const start=getStart(),end=getEnd();
  const recEntries=jobs.filter(j=>j.paid&&j.paidDate>=start&&j.paidDate<=end).map(j=>({date:j.paidDate,desc:`${j.id} — ${SVM[j.service]?.name||j.service}`,value:j.value,type:'receber',method:j.payMethod||'—'}));
  const pagEntries=bills.filter(b=>b.status==='pago'&&b.paidDate>=start&&b.paidDate<=end).map(b=>({date:b.paidDate,desc:b.description,value:b.value,type:'pagar',method:b.payMethod||'—'}));
  let entries=type==='receber'?recEntries:type==='pagar'?pagEntries:[...recEntries,...pagEntries];
  entries.sort((a,b)=>b.date.localeCompare(a.date));
  const totRec=recEntries.reduce((s,e)=>s+e.value,0);const totPag=pagEntries.reduce((s,e)=>s+e.value,0);
  return <>
    <div className="fbar">
      <span className="lbl">Tipo:</span>
      {[['all','Todos'],['receber','A Receber'],['pagar','A Pagar']].map(([k,l])=><button key={k} className={`btn ${type===k?'bp':'bs'} bsm`} onClick={()=>setTy(k)}>{l}</button>)}
      <span className="lbl" style={{marginLeft:10}}>Período:</span>
      {[['day','Dia'],['week','Semana'],['month','Mês'],['year','Ano'],['custom','Personalizado']].map(([k,l])=><button key={k} className={`btn ${range===k?'bp':'bs'} bsm`} onClick={()=>setRa(k)}>{l}</button>)}
      {range==='custom'&&<><input className="fi" type="date" style={{width:130,padding:'4px 8px',fontSize:11}} value={customFrom} onChange={e=>setCF(e.target.value)}/><span style={{color:'var(--t3)',fontSize:11}}>até</span><input className="fi" type="date" style={{width:130,padding:'4px 8px',fontSize:11}} value={customTo} onChange={e=>setCT(e.target.value)}/></>}
    </div>
    <div className="fsum"><div className="fbox"><div className="l">Entradas</div><div className="v ok">{fmt(totRec)}</div></div><div className="fbox"><div className="l">Saídas</div><div className="v err">{fmt(totPag)}</div></div><div className="fbox"><div className="l">Saldo</div><div className="v" style={{color:totRec-totPag>=0?'var(--ok)':'var(--err)'}}>{fmt(totRec-totPag)}</div></div></div>
    <div className="cd"><div className="cd-h"><h3>Movimentações ({entries.length})</h3></div>
    {entries.length===0?<div className="empty"><h4>Nenhuma movimentação</h4><p>Ajuste os filtros para ver resultados</p></div>:
    <div className="tb-wrap"><table className="tb"><thead><tr><th>Data</th><th>Descrição</th><th>Forma Pgto</th><th>Tipo</th><th>Valor</th></tr></thead><tbody>
    {entries.map((e,i)=><tr key={i}><td>{fmtD(e.date)}</td><td style={{color:'var(--t1)',fontWeight:500,maxWidth:250,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{e.desc}</td><td><span className="chip" style={{background:'rgba(100,116,139,.1)',color:'var(--t2)'}}>{e.method}</span></td><td><span className="chip" style={{background:e.type==='receber'?'rgba(16,185,129,.1)':'rgba(239,68,68,.1)',color:e.type==='receber'?'var(--ok)':'var(--err)'}}>{e.type==='receber'?'Entrada':'Saída'}</span></td><td style={{fontFamily:'var(--m)',fontWeight:700,color:e.type==='receber'?'var(--ok)':'var(--err)'}}>{e.type==='receber'?'+':'-'}{fmt(e.value)}</td></tr>)}
    </tbody></table></div>}</div>
  </>;
}

// Pipeline
function PgPipe({jobs,clients,users,services}){const SvcN=({id})=>services.find(s=>s.id===id)?.name||id;const cN=id=>clients.find(c=>c.id===id)?.name||'—';const uN=id=>users.find(u=>u.id===id)?.name;
  return <div className="pipe">{STATUS_FLOW.map(st=>{const col=jobs.filter(j=>j.status===st.id);return <div className="pcol" key={st.id}><div className="pcol-h"><span className="d" style={{background:st.color}}/><span className="t">{st.label}</span><span className="c">{col.length}</span></div>{col.map(j=><div className="pcard" key={j.id}><div className="pid">{j.id}</div><div className="pcl">{cN(j.clientId)}</div><div className="psvc"><SvcN id={j.service}/></div><div className="pfoo"><span className="pval">{fmt(j.value)}</span><span className="pdt">{fmtD(j.date)}</span></div>{j.assignedTo&&<div className="pa">→ {uN(j.assignedTo)}</div>}</div>)}</div>;})}</div>;
}

// Assign
function PgAssign({jobs,setJobs,clients,users,services,token}){const SvcN=({id})=>services.find(s=>s.id===id)?.name||id;const un=jobs.filter(j=>!j.assignedTo);const techs=users.filter(u=>u.active&&(u.role==='tecnico'||u.role==='admin'));const cN=id=>clients.find(c=>c.id===id)?.name||'—';
  const[sel,setSel]=useState({});const { addToast } = useToast();
  const assign=async jid=>{if(sel[jid]){try{await fetch(`/api/jobs/${jid}/assign`,{method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({assigned_to:Number(sel[jid])})});setJobs(p=>p.map(j=>j.id===jid?{...j,assignedTo:Number(sel[jid])}:j));setSel(p=>{const n={...p};delete n[jid];return n;});addToast('Trabalho alocado!','success');}catch(e){addToast('Erro API','error');}}};
  return <><div className="fsum" style={{gridTemplateColumns:'1fr 1fr'}}><div className="fbox"><div className="l">Sem Atribuição</div><div className="v err">{un.length}</div></div><div className="fbox"><div className="l">Funcionários</div><div className="v ok">{techs.length}</div></div></div>
  <div className="cd"><div className="cd-h"><h3>Direcionamento</h3></div>{un.length===0?<div className="empty"><h4>Todos atribuídos!</h4></div>:<div className="tb-wrap"><table className="tb"><thead><tr><th>ID</th><th>Cliente</th><th>Serviço</th><th>Origem</th><th>Atribuir a</th><th>Ação</th></tr></thead><tbody>
  {un.map(j=><tr key={j.id}><td style={{fontFamily:'var(--m)',fontSize:10}}>{j.id}</td><td style={{color:'var(--t1)',fontWeight:500}}>{cN(j.clientId)}</td><td><SvcN id={j.service}/></td><td><span className="chip" style={{background:j.origin==='client'?'rgba(139,92,246,.1)':'rgba(14,165,233,.1)',color:j.origin==='client'?'var(--pu)':'var(--ac)'}}>{j.origin==='client'?'Portal':'Admin'}</span></td><td><select className="fs" style={{padding:'4px 8px',fontSize:11}} value={sel[j.id]||''} onChange={e=>setSel(p=>({...p,[j.id]:e.target.value}))}><option value="">Selecionar...</option>{techs.map(u=><option key={u.id} value={u.id}>{u.name}</option>)}</select></td><td><button className="btn bp bsm" disabled={!sel[j.id]} onClick={()=>assign(j.id)}>{I.assign} Atribuir</button></td></tr>)}
  </tbody></table></div>}</div></>;
}

// My Work
function PgMyWork({jobs,clients,currentUser,services}){const SvcN=({id})=>services.find(s=>s.id===id)?.name||id;const my=jobs.filter(j=>j.assignedTo===currentUser.id);const cN=id=>clients.find(c=>c.id===id)?.name||'—';
  const act=my.filter(j=>!['entregue','finalizado'].includes(j.status));const done=my.filter(j=>['entregue','finalizado'].includes(j.status));
  return <><div className="stats" style={{gridTemplateColumns:'repeat(4,1fr)'}}><div className="st"><div className="sv">{my.length}</div><div className="sl">Total</div></div><div className="st"><div className="sv" style={{color:'var(--wn)'}}>{act.length}</div><div className="sl">Em Andamento</div></div><div className="st"><div className="sv" style={{color:'var(--ok)'}}>{done.length}</div><div className="sl">Concluídos</div></div><div className="st"><div className="sv" style={{color:'var(--ac)'}}>{fmt(my.reduce((s,j)=>s+j.value,0))}</div><div className="sl">Valor</div></div></div>
  {act.length>0&&<><h3 style={{fontSize:13,fontWeight:600,marginBottom:8,color:'var(--t2)'}}>Em Andamento</h3><div className="cd" style={{marginBottom:16}}><div className="tb-wrap"><table className="tb"><thead><tr><th>ID</th><th>Cliente</th><th>Serviço</th><th>Data</th><th>Status</th></tr></thead><tbody>{act.map(j=><tr key={j.id}><td style={{fontFamily:'var(--m)',fontSize:10}}>{j.id}</td><td style={{color:'var(--t1)',fontWeight:500}}>{cN(j.clientId)}</td><td><SvcN id={j.service}/></td><td>{fmtD(j.date)}</td><td><StB id={j.status}/></td></tr>)}</tbody></table></div></div></>}
  {done.length>0&&<><h3 style={{fontSize:13,fontWeight:600,marginBottom:8,color:'var(--t2)'}}>Concluídos</h3><div className="cd"><div className="tb-wrap"><table className="tb"><thead><tr><th>ID</th><th>Cliente</th><th>Serviço</th><th>Data</th><th>Status</th></tr></thead><tbody>{done.map(j=><tr key={j.id}><td style={{fontFamily:'var(--m)',fontSize:10}}>{j.id}</td><td style={{color:'var(--t1)',fontWeight:500}}>{cN(j.clientId)}</td><td><SvcN id={j.service}/></td><td>{fmtD(j.date)}</td><td><StB id={j.status}/></td></tr>)}</tbody></table></div></div></>}
  {my.length===0&&<div className="empty"><h4>Nenhum trabalho atribuído</h4></div>}</>;
}

// ═══ CLIENT PAGES ═══
function CliSvc({services}){return <><div style={{marginBottom:14}}><h3 style={{fontSize:14,fontWeight:600}}>Nossos Serviços</h3></div><div className="svc-grid">{services.map(s=><div className="svcc" key={s.id}><div className="si2" style={{background:s.color||'var(--ac)'}}>{I.svc}</div><div className="sn">{s.name}</div><div className="sp">a partir de {fmt(s.base_price)}</div></div>)}</div></>;}

function CliNew({jobs,setJobs,clientId,services,token}){const[step,setStep]=useState(1);const[sel,setSel]=useState(null);const[desc,setDesc]=useState('');
  const SVM=Object.fromEntries(services.map(s=>[s.id,s]));
  const[files,setFiles]=useState({photos:[],scans:{sup:null,inf:null,ext_sup:null,ext_inf:null}});
  const SvcN=({id})=>services.find(s=>s.id===id)?.name||id;
  const[pendingSlot,setPendingSlot]=useState(null);const scanInputRef=useRef(null);const photoInputRef=useRef(null);
  const[submitting,setSubmitting]=useState(false);const{addToast}=useToast();
  const handleScanClick=(slotId)=>{setPendingSlot(slotId);scanInputRef.current.value='';scanInputRef.current.click();};
  const handleScanChange=(e)=>{const f=e.target.files[0];if(!f)return;setFiles(p=>({...p,scans:{...p.scans,[pendingSlot]:{name:f.name,url:URL.createObjectURL(f),size:f.size,file:f}}}));};
  const handlePhotoChange=(e)=>{const arr=Array.from(e.target.files);if(!arr.length)return;setFiles(p=>({...p,photos:[...p.photos,...arr.map(f=>({name:f.name,url:URL.createObjectURL(f),size:f.size,file:f}))]}));};
  const submit=async()=>{
    if(submitting)return;setSubmitting(true);
    try{
      const svc=SVM[sel];
      const r=await fetch('/api/jobs',{method:'POST',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({client_id:clientId,service_id:sel,value:svc?.base_price||0,notes:desc,origin:'client'})});
      const d=await r.json();if(!r.ok)throw new Error(d.error||'Erro ao criar solicitação');
      const jobId=d.id;
      for(const[slot,f]of Object.entries(files.scans)){if(f?.file){const fd=new FormData();fd.append('file',f.file);fd.append('file_type',`scan_${slot}`);await fetch(`/api/jobs/${jobId}/files`,{method:'POST',headers:{'Authorization':`Bearer ${token}`},body:fd});}}
      for(const p of files.photos){if(p?.file){const fd=new FormData();fd.append('file',p.file);fd.append('file_type','photo');await fetch(`/api/jobs/${jobId}/files`,{method:'POST',headers:{'Authorization':`Bearer ${token}`},body:fd});}}
      setJobs(prev=>[{id:jobId,clientId,service:sel,status:'recebido',date:new Date().toISOString().split('T')[0],value:svc?.base_price||0,paid:false,paidDate:null,payMethod:null,notes:desc,assignedTo:null,origin:'client',files},...prev]);
      setStep(1);setSel(null);setDesc('');setFiles({photos:[],scans:{sup:null,inf:null,ext_sup:null,ext_inf:null}});
      addToast('Solicitação enviada com sucesso!','success');
    }catch(e){addToast(e.message,'error');}finally{setSubmitting(false);}
  };
  return <><div style={{marginBottom:14}}><h3 style={{fontSize:14,fontWeight:600}}>Nova Solicitação — Passo {step}/3</h3></div>
  <div style={{display:'flex',gap:5,marginBottom:20}}>{[1,2,3].map(i=><div key={i} style={{flex:1,height:3,borderRadius:2,background:i<=step?'var(--ac)':'var(--bd)',transition:'.3s'}}/>)}</div>
  {step===1&&<><div className="svc-grid">{services.map(s=><div className={`svcc ${sel===s.id?'sel':''}`} key={s.id} onClick={()=>setSel(s.id)}><div className="si2" style={{background:s.color||'var(--ac)'}}>{I.svc}</div><div className="sn">{s.name}</div><div className="sp">{fmt(s.base_price)}</div></div>)}</div><div style={{marginTop:16,display:'flex',justifyContent:'flex-end'}}><button className="btn bp" disabled={!sel} onClick={()=>setStep(2)}>Próximo {I.chev}</button></div></>}
  {step===2&&<>
    <div style={{fontSize:12,fontWeight:600,marginBottom:8,color:'var(--t2)'}}>Escaneamentos</div>
    <input ref={scanInputRef} type="file" accept=".ply,.stl,.obj,.dcm,.zip,.rar" style={{display:'none'}} onChange={handleScanChange}/>
    {SCAN_SLOTS.map(sl=>{const f=files.scans[sl.id];const ext=f?f.name.split('.').pop().toLowerCase():'';const can3d=['stl','obj','ply'].includes(ext)&&f?.url;return <div className="file-slot" key={sl.id} style={f?{flexDirection:'column',alignItems:'stretch',gap:0,padding:0,overflow:'hidden'}:{}}>{f?<><div style={{display:'flex',alignItems:'center',gap:12,padding:'12px 14px'}}><div className="fs-label">{sl.label}</div><div className="scan-mini-viewer"><div className="smv-icon">{I.cube}</div><div className="smv-ext">{ext.toUpperCase()}</div></div><div className="fs-name" style={{flex:1}}>{f.name}{f.size?<span style={{fontSize:10,color:'var(--t3)',marginLeft:6,fontFamily:'var(--m)'}}>{(f.size/1024/1024).toFixed(1)}MB</span>:null}</div><button className="btn berr bsm" onClick={()=>setFiles(p=>({...p,scans:{...p.scans,[sl.id]:null}}))}>  {I.trash}</button></div>{can3d?<div style={{height:220,borderTop:'1px solid rgba(255,255,255,0.06)',background:'rgba(0,0,0,0.3)'}}><ScanViewer3D url={f.url} name={f.name}/></div>:<div style={{height:44,borderTop:'1px solid rgba(255,255,255,0.06)',display:'flex',alignItems:'center',justifyContent:'center',gap:6,color:'var(--t3)',fontSize:11}}>{I.cube}<span>Visualização não disponível para este formato</span></div>}</>:<div style={{display:'flex',alignItems:'center',gap:12,padding:'14px'}}><div className="fs-label">{sl.label}</div><button className="btn bs bsm" onClick={()=>handleScanClick(sl.id)}>{I.plus} Carregar</button></div>}</div>;})}
    <div style={{fontSize:12,fontWeight:600,marginBottom:8,marginTop:12,color:'var(--t2)'}}>Fotos</div>
    <input ref={photoInputRef} type="file" accept="image/*" multiple style={{display:'none'}} onChange={handlePhotoChange}/>
    <div className="upload" onClick={()=>photoInputRef.current.click()}><div style={{color:'var(--t3)',marginBottom:6}}>{I.cam}</div><p>Clique para adicionar foto</p></div>
    {files.photos.length>0&&<div style={{display:'flex',gap:6,flexWrap:'wrap',marginTop:8}}>{files.photos.map((p,i)=><span key={i} className="chip" style={{background:'var(--bg3)',border:'1px solid var(--bd)'}}>{p.name}<span style={{cursor:'pointer',marginLeft:4}} onClick={()=>setFiles(prev=>({...prev,photos:prev.photos.filter((_,idx)=>idx!==i)}))}>×</span></span>)}</div>}
    <div className="fg" style={{marginTop:12}}><label className="fl">Descrição do Caso</label><textarea className="fta" rows={3} value={desc} onChange={e=>setDesc(e.target.value)} placeholder="Região, implantes, materiais..."/></div>
    <div style={{display:'flex',justifyContent:'space-between'}}><button className="btn bs" onClick={()=>setStep(1)}>{I.back} Voltar</button><button className="btn bp" onClick={()=>setStep(3)}>Próximo {I.chev}</button></div></>}
  {step===3&&<><div className="cd" style={{marginBottom:14}}><div style={{padding:16}}><div style={{display:'flex',justifyContent:'space-between'}}><div><div style={{fontSize:10,color:'var(--t3)'}}>Serviço</div><div style={{fontWeight:600}}><SvcN id={sel}/></div></div><div style={{textAlign:'right'}}><div style={{fontSize:10,color:'var(--t3)'}}>Valor</div><div style={{fontFamily:'var(--m)',fontWeight:700,fontSize:16,color:'var(--ac)'}}>{fmt(SVM[sel]?.base_price||0)}</div></div></div><div style={{marginTop:8,fontSize:10,color:'var(--t3)'}}>Arquivos: {Object.values(files.scans).filter(Boolean).length} escaneamento(s), {files.photos.length} foto(s)</div><div style={{marginTop:4,fontSize:10,color:'var(--t3)'}}>Desc.</div><div style={{fontSize:12,color:'var(--t2)'}}>{desc||'Sem descrição'}</div></div></div>
  <div style={{display:'flex',justifyContent:'space-between'}}><button className="btn bs" disabled={submitting} onClick={()=>setStep(2)}>{I.back} Voltar</button><button className="btn bp" disabled={submitting} onClick={submit}>{submitting?'Enviando...':(<>{I.check} Confirmar</>)}</button></div></>}
  </>;
}

function CliOrders({jobs,services}){const SvcN=({id})=>services.find(s=>s.id===id)?.name||id;const[sel,setSel]=useState(null);const[preview,setPrev]=useState(null);
  if(sel)return <><button className="btn bg" onClick={()=>{setSel(null);setPrev(null);}} style={{marginBottom:10}}>{I.back} Voltar</button>
  <div className="cd"><div style={{padding:18}}><div style={{display:'flex',justifyContent:'space-between',marginBottom:4}}><div><span style={{fontFamily:'var(--m)',fontSize:11,color:'var(--t3)'}}>{sel.id}</span><h3 style={{fontSize:15,fontWeight:600,marginTop:2}}><SvcN id={sel.service}/></h3></div><div style={{textAlign:'right'}}><div style={{fontFamily:'var(--m)',fontWeight:700,fontSize:18,color:'var(--ac)'}}>{fmt(sel.value)}</div><span className="chip" style={{background:sel.paid?'rgba(16,185,129,.1)':'rgba(239,68,68,.1)',color:sel.paid?'var(--ok)':'var(--err)'}}>{sel.paid?'Pago':'Pendente'}</span></div></div>
  <FlowT status={sel.status}/>
  <div className="tabs" style={{marginTop:14}}><div className={`tab ${!preview?'on':''}`} onClick={()=>setPrev(null)}>Info</div><div className={`tab ${preview?'on':''}`} onClick={()=>setPrev('files')}>Arquivos</div></div>
  {!preview&&<><div style={{fontSize:10,color:'var(--t3)'}}>Obs.</div><div style={{fontSize:12,color:'var(--t2)'}}>{sel.notes}</div></>}
  {preview==='files'&&<><div style={{fontSize:12,fontWeight:600,marginBottom:6,color:'var(--t2)'}}>Escaneamentos</div>{SCAN_SLOTS.map(sl=>{const f=sel.files?.scans?.[sl.id];return <div className="file-slot" key={sl.id}><div className="fs-label">{sl.label}</div>{f?<><div className="scan-mini-viewer"><div className="smv-icon">{I.cube}</div><div className="smv-ext">{f.name.split('.').pop().toUpperCase()}</div></div><div className="fs-name" style={{flex:1}}>{f.name}</div></>:<span style={{fontSize:10,color:'var(--t3)'}}>—</span>}</div>;})}<div style={{fontSize:12,fontWeight:600,marginTop:10,marginBottom:6,color:'var(--t2)'}}>Fotos ({sel.files?.photos?.length||0})</div>{sel.files?.photos?.length>0?<div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(80px,1fr))',gap:6}}>{sel.files.photos.map((p,i)=><div key={i} style={{background:'var(--bg3)',border:'1px solid var(--bd)',borderRadius:6,padding:4,textAlign:'center'}}>{p.url?<img src={p.url} alt="" style={{maxWidth:'100%',maxHeight:50,borderRadius:3}}/>:<span style={{color:'var(--t3)',fontSize:10}}>Foto</span>}<div style={{fontSize:8,color:'var(--t3)',marginTop:2}}>{p.name}</div></div>)}</div>:<div style={{fontSize:11,color:'var(--t3)'}}>Nenhuma foto</div>}</>}
  </div></div></>;
  return <div className="cd"><div className="tb-wrap"><table className="tb"><thead><tr><th>ID</th><th>Serviço</th><th>Data</th><th>Status</th><th>Valor</th><th></th></tr></thead><tbody>{jobs.map(j=><tr key={j.id}><td style={{fontFamily:'var(--m)',fontSize:10}}>{j.id}</td><td><SvcN id={j.service}/></td><td>{fmtD(j.date)}</td><td><StB id={j.status}/></td><td style={{fontFamily:'var(--m)',fontWeight:600}}>{fmt(j.value)}</td><td><button className="btn bg bsm" onClick={()=>setSel(j)}>Detalhes {I.chev}</button></td></tr>)}</tbody></table></div></div>;
}

function CliFin({jobs,services}){const SvcN=({id})=>services.find(s=>s.id===id)?.name||id;const paid=jobs.filter(j=>j.paid).reduce((s,j)=>s+j.value,0);const pend=jobs.filter(j=>!j.paid).reduce((s,j)=>s+j.value,0);
  return <><div className="fsum"><div className="fbox"><div className="l">Total</div><div className="v wn">{fmt(paid+pend)}</div></div><div className="fbox"><div className="l">Pago</div><div className="v ok">{fmt(paid)}</div></div><div className="fbox"><div className="l">Pendente</div><div className="v err">{fmt(pend)}</div></div></div>
  <div className="cd"><div className="cd-h"><h3>Extrato</h3></div><div className="tb-wrap"><table className="tb"><thead><tr><th>ID</th><th>Serviço</th><th>Data</th><th>Valor</th><th>Pgto</th></tr></thead><tbody>{jobs.map(j=><tr key={j.id}><td style={{fontFamily:'var(--m)',fontSize:10}}>{j.id}</td><td><SvcN id={j.service}/></td><td>{fmtD(j.date)}</td><td style={{fontFamily:'var(--m)',fontWeight:600}}>{fmt(j.value)}</td><td><span className="chip" style={{background:j.paid?'rgba(16,185,129,.1)':'rgba(239,68,68,.1)',color:j.paid?'var(--ok)':'var(--err)'}}>{j.paid?j.payMethod||'Pago':'Pendente'}</span></td></tr>)}</tbody></table></div></div></>;
}

// ═══ MAIN APP ═══
window.App = function App(){
  const [token, setToken] = useState(localStorage.getItem('aptoken') || null);
  const [curUser, setCurUser] = useState(JSON.parse(localStorage.getItem('apuser') || '{"name":"Admin"}'));
  const [mode, setMode] = useState(localStorage.getItem('apmode') || 'admin');
  const [loading, setLoading] = useState(false);
  const[pg,setPg]=useState('dashboard');const[cpg,setCpg]=useState('services');
  const[jobs,setJobs]=useState([]);const[users,setUsers]=useState([]);const[clients,setClients]=useState([]);const[bills,setBills]=useState([]);
  const[services,setServices]=useState([]);const[categories,setCategories]=useState([]);
  const[sideOpen,setSideOpen]=useState(false);

  useEffect(() => {
    if (!token) return;
    const fetchAll = async () => {
      try {
        setLoading(true);
        const headers = { 'Authorization': `Bearer ${token}` };
        const [uR, cR, jR, bR, sR, catR] = await Promise.all([
          fetch('/api/users', { headers }),
          fetch('/api/clients', { headers }),
          fetch('/api/jobs', { headers }),
          fetch('/api/bills', { headers }),
          fetch('/api/services'),
          fetch('/api/service-categories', { headers }),
        ]);
        if(uR.ok) setUsers(await uR.json());
        if(cR.ok) setClients(await cR.json());
        if(jR.ok) {
           const jData = await jR.json();
           setJobs(jData.map(j => ({ id: j.id, clientId: j.client_id, service: j.service_id, value: Number(j.value), status: j.status, date: j.created_at?.split('T')[0] || j.created_at, paid: j.paid, paidDate: j.paid_date?.split('T')[0] || null, payMethod: j.pay_method, notes: j.notes, assignedTo: j.assigned_to, origin: j.origin })));
        }
        if(bR.ok) {
           const bData = await bR.json();
           setBills(bData.map(b => ({ id: b.id, description: b.description, supplier: b.supplier, category: b.category, value: Number(b.value), dueDate: b.due_date?.split('T')[0], paidDate: b.paid_date?.split('T')[0], payMethod: b.pay_method, status: b.status, recurring: b.recurring, notes: b.notes })));
        }
        if(sR.ok) setServices(await sR.json());
        if(catR.ok) setCategories(await catR.json());
      } catch(e) { console.error('API Error', e); } finally { setLoading(false); }
    };
    fetchAll();
  }, [token]);

  const logout = () => { localStorage.removeItem('aptoken'); localStorage.removeItem('apuser'); localStorage.removeItem('apmode'); setToken(null); };
  const[finSub,setFinSub]=useState('overview');const[flowSub,setFlowSub]=useState('pipeline');const[cfgSub,setCfgSub]=useState('categories');
  const cliJobs=jobs.filter(j=>j.clientId===curUser?.id);
  const un=jobs.filter(j=>!j.assignedTo).length;const unp=jobs.filter(j=>!j.paid).length;
  const ovBills=bills.filter(b=>b.status!=='pago'&&b.dueDate<new Date().toISOString().split('T')[0]).length;

  const aMenu=[
    {id:'dashboard',label:'Dashboard',icon:I.dash},
    {id:'mywork',label:'Meus Trabalhos',icon:I.mywork},
    {id:'clients',label:'Clientes',icon:I.client},
    {id:'jobs',label:'Trabalhos',icon:I.work,badge:jobs.filter(j=>j.status==='recebido').length},
    {id:'finance',label:'Financeiro',icon:I.money,badge:unp+ovBills>0?unp+ovBills:0,bw:true,subs:[{id:'overview',label:'Visão Geral'},{id:'payments',label:'Pagamentos'},{id:'bills',label:'Contas a Pagar'},{id:'cashflow',label:'Fluxo de Caixa'}]},
    {id:'flow',label:'Fluxo de Trabalho',icon:I.flow,badge:un>0?un:0,bw:true,subs:[{id:'pipeline',label:'Pipeline'},{id:'assign',label:'Direcionamento'}]},
    {id:'config',label:'Configurações',icon:I.settings,subs:[{id:'categories',label:'Categorias'},{id:'service_types',label:'Tipos de Serviço'},{id:'users',label:'Usuários'}]},
  ];
  const cMenu=[{id:'services',label:'Serviços',icon:I.svc},{id:'new_order',label:'Nova Solicitação',icon:I.plus},{id:'my_orders',label:'Meus Trabalhos',icon:I.work,badge:cliJobs.filter(j=>!['entregue','finalizado'].includes(j.status)).length},{id:'cli_finance',label:'Financeiro',icon:I.money}];
  const curMenu=mode==='admin'?aMenu:cMenu;const curPg=mode==='admin'?pg:cpg;const setCurPg=mode==='admin'?setPg:setCpg;
  const menuClick=item=>{setCurPg(item.id);if(item.id==='finance')setFinSub('overview');if(item.id==='flow')setFlowSub('pipeline');if(item.id==='config')setCfgSub('categories');setSideOpen(false);};
  let pageTitle=curMenu.find(m=>m.id===curPg)?.label||'';
  if(mode==='admin'&&pg==='finance')pageTitle=`Financeiro — ${{overview:'Visão Geral',payments:'Pagamentos',bills:'Contas a Pagar',cashflow:'Fluxo de Caixa'}[finSub]}`;
  if(mode==='admin'&&pg==='flow')pageTitle=`Fluxo — ${{pipeline:'Pipeline',assign:'Direcionamento'}[flowSub]}`;
  if(mode==='admin'&&pg==='config')pageTitle=`Configurações — ${{categories:'Categorias',service_types:'Tipos de Serviço',users:'Usuários'}[cfgSub]}`;

  if (!token) return <ToastProvider><style>{css}</style><PgLogin onLog={(t,m,u)=>{setToken(t);setMode(m);setCurUser(u);}} /></ToastProvider>;
  return <ToastProvider><style>{css}</style><div className="app">
    <div className={`sb-overlay${sideOpen?' open':''}`} onClick={()=>setSideOpen(false)}/>
    <aside className={`sb${sideOpen?' open':''}`}>
      <div className="sb-brand"><div className="sb-logo">{I.stl}</div><div><h1>AP Core Planning</h1><div className="sub">Laboratório Digital</div></div></div>
      <div className="sb-sec"><div className="sb-lbl">{mode==='admin'?'Gestão':'Portal'}</div>
      {curMenu.map(item=><div key={item.id}><div className={`si ${curPg===item.id?'on':''}`} onClick={()=>menuClick(item)}>{item.icon}{item.label}{item.badge>0&&<span className={`sb-bg${item.bw?' w':''}`}>{item.badge}</span>}</div>
      {item.subs&&curPg===item.id&&item.subs.map(sub=>{const ss=item.id==='finance'?finSub:item.id==='flow'?flowSub:cfgSub;const setSS=item.id==='finance'?setFinSub:item.id==='flow'?setFlowSub:setCfgSub;return <div key={sub.id} className={`ssub ${ss===sub.id?'on':''}`} onClick={()=>{setSS(sub.id);setSideOpen(false);}}>{sub.label}</div>;})}</div>)}</div>
      <div className="sb-ft"><div className="sb-u"><div className="av">{(curUser?.name||'U').slice(0,2).toUpperCase()}</div><div style={{flex:1,minWidth:0}}><div style={{fontSize:12,fontWeight:500,color:'var(--t1)',whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{curUser?.name||'Usuário'}</div><div style={{fontSize:10,color:'var(--t3)'}}>{mode==='admin'?RM[curUser?.role]?.label||'Admin':curUser?.clinic||''}</div></div><div onClick={logout} style={{cursor:'pointer', marginLeft:'10px'}}>{I.logout}</div></div></div>
    </aside>
    <div className="mn"><div className="top"><div style={{display:'flex',alignItems:'center',gap:12}}><button className="hamburger" onClick={()=>setSideOpen(!sideOpen)}>{I.menu}</button><div className="top-t">{mode==='admin'?pageTitle:curMenu.find(m=>m.id===cpg)?.label}</div></div><div className="top-a"></div></div>
    <div className="pg">
      {mode==='admin'&&pg==='dashboard'&&<PgDash jobs={jobs} clients={clients} users={users} services={services}/>}
      {mode==='admin'&&pg==='mywork'&&<PgMyWork jobs={jobs} clients={clients} currentUser={curUser} services={services}/>}
      {mode==='admin'&&pg==='clients'&&<PgClients clients={clients} setClients={setClients} jobs={jobs} services={services} token={token}/>}
      {mode==='admin'&&pg==='jobs'&&<PgJobs jobs={jobs} setJobs={setJobs} clients={clients} users={users} services={services} token={token}/>}
      {mode==='admin'&&pg==='finance'&&finSub==='overview'&&<PgFinOv jobs={jobs} clients={clients}/>}
      {mode==='admin'&&pg==='finance'&&finSub==='payments'&&<PgFinPay jobs={jobs} setJobs={setJobs} clients={clients} services={services} token={token}/>}
      {mode==='admin'&&pg==='finance'&&finSub==='bills'&&<PgBills bills={bills} setBills={setBills} token={token}/>}
      {mode==='admin'&&pg==='finance'&&finSub==='cashflow'&&<PgCashFlow jobs={jobs} bills={bills} services={services}/>}
      {mode==='admin'&&pg==='flow'&&flowSub==='pipeline'&&<PgPipe jobs={jobs} clients={clients} users={users} services={services}/>}
      {mode==='admin'&&pg==='flow'&&flowSub==='assign'&&<PgAssign jobs={jobs} setJobs={setJobs} clients={clients} users={users} services={services} token={token}/>}
      {mode==='admin'&&pg==='config'&&cfgSub==='categories'&&<PgServiceCategories categories={categories} setCategories={setCategories} token={token}/>}
      {mode==='admin'&&pg==='config'&&cfgSub==='service_types'&&<PgServiceTypes services={services} setServices={setServices} categories={categories} token={token}/>}
      {mode==='admin'&&pg==='config'&&cfgSub==='users'&&<PgUsers users={users} setUsers={setUsers} token={token}/>}
      {mode==='client'&&cpg==='services'&&<CliSvc services={services}/>}
      {mode==='client'&&cpg==='new_order'&&<CliNew jobs={jobs} setJobs={setJobs} clientId={curUser?.id||1} services={services} token={token}/>}
      {mode==='client'&&cpg==='my_orders'&&<CliOrders jobs={cliJobs} services={services}/>}
      {mode==='client'&&cpg==='cli_finance'&&<CliFin jobs={cliJobs} services={services}/>}
    </div></div>
  </div></ToastProvider>;
}
