import React, { useState, useEffect, useCallback } from 'react'; import { FlaskConical, Wallet, Clock, AlertTriangle, CheckCircle2, RefreshCw, Inbox, Plus, ArrowDownToLine, X, Stethoscope, Building2, Briefcase } from 'lucide-react'; import { HybridBackend } from '../services/backend.ts'; import { PageHeader } from '../components/PageHeader.tsx'; import { useToast } from '../contexts/ToastContext.tsx'; const fmtBRL = (v: number) => 'R$ ' + Number(v || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 }); const CLI_TIPO = [ { v: 'clinica', label: 'Clínica', icon: Building2 }, { v: 'consultorio', label: 'Consultório', icon: Briefcase }, { v: 'dentista', label: 'Dentista', icon: Stethoscope }, ]; // Painel do LABORATÓRIO (protético): aparece quando o workspace ativo é tipo='laboratorio'. // Resumo a partir do que já existe — a Bancada (OS) + o financeiro do workspace do lab. export const LabHomeView: React.FC = () => { const ws: any = HybridBackend.getActiveWorkspace(); const toast = useToast(); const [os, setOs] = useState([]); const [fin, setFin] = useState([]); const [indicadas, setIndicadas] = useState([]); const [loading, setLoading] = useState(false); const [showEntrada, setShowEntrada] = useState(false); const [importando, setImportando] = useState(null); const carregar = useCallback(async () => { setLoading(true); try { const [b, f, ind] = await Promise.all([HybridBackend.getProteseBancada(), HybridBackend.getLabFechamento(), HybridBackend.getLabOsIndicadas()]); setOs(Array.isArray(b) ? b : []); setFin(Array.isArray(f) ? f : []); setIndicadas(Array.isArray(ind) ? ind : []); } catch { /* silent */ } finally { setLoading(false); } }, []); useEffect(() => { carregar(); }, [carregar]); const importar = async (osId: string) => { setImportando(osId); try { const r = await HybridBackend.vincularOsLab(osId); if (r?.error) throw new Error(r.error); toast.success('OS IMPORTADA PARA O LABORATÓRIO.'); carregar(); } catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); } finally { setImportando(null); } }; const ativas = os.filter(o => o.status !== 'entregue' && o.status !== 'cancelado'); const atrasadas = os.filter(o => o.atrasada).length; const ymMes = new Date().toISOString().slice(0, 7); const entreguesMes = os.filter(o => o.status === 'entregue' && (o.updated_at || '').slice(0, 7) === ymMes).length; // Faturamento do mês: do fechamento do próprio lab (custo das OS entregues no mês). const fatMes = Number(fin.find(m => m.mes === ymMes)?.faturado || 0); const KPI: React.FC<{ titulo: string; valor: string; cor: string; Icon: any; sub?: string }> = ({ titulo, valor, cor, Icon, sub }) => (

{titulo}

{valor}
{sub &&

{sub}

}
); // Distribuição por status (fila de produção). const STATUS_ORD = ['solicitado', 'recebido', 'producao', 'prova', 'ajuste', 'finalizado']; const STATUS_LABEL: Record = { solicitado: 'Solicitado', recebido: 'Recebido', producao: 'Produção', prova: 'Prova', ajuste: 'Ajuste', finalizado: 'Finalizado' }; const porStatus = STATUS_ORD.map(s => ({ s, n: ativas.filter(o => o.status === s).length })).filter(x => x.n > 0); return (

{ws?.nome || 'Laboratório'}

Área de gestão do protético

{loading ?
: ( <>
{/* OS indicadas ao laboratório aguardando importação */} {indicadas.length > 0 && (

OS indicadas a você ({indicadas.length})

Importe para entrar na produção e financeiro
{indicadas.map(o => (

{o.tipo}

{o.cliente_nome || 'Cliente'}{o.prazo_entrega ? ` · prazo ${(o.prazo_entrega).slice(0, 10).split('-').reverse().join('/')}` : ''}

{fmtBRL(o.custo)}
))}
)} {/* Fila por status */}

Fila de produção

{porStatus.length === 0 ? (
Nenhuma OS em produção.
) : (
{porStatus.map(({ s, n }) => (

{STATUS_LABEL[s]}

{n}

))}
)}
{/* Próximas entregas */}

Próximas entregas

{ativas.filter(o => o.prazo_entrega).length === 0 ? (
Sem prazos agendados.
) : (
{['Paciente', 'Tipo', 'Prazo', 'Status', 'Valor'].map(h => )} {ativas.filter(o => o.prazo_entrega).sort((a, b) => (a.prazo_entrega || '').localeCompare(b.prazo_entrega || '')).slice(0, 12).map(o => ( ))}
{h}
{o.paciente_nome || '—'} {o.tipo || '—'} {(o.prazo_entrega || '').slice(0, 10).split('-').reverse().join('/')} {STATUS_LABEL[o.status] || o.status} {fmtBRL(o.custo)}
)}
)} {showEntrada && setShowEntrada(false)} onSaved={() => { setShowEntrada(false); carregar(); }} />}
); }; // ── Modal: dar entrada em OS pelo lado do laboratório ──────────────────────── const LabEntradaModal: React.FC<{ onClose: () => void; onSaved: () => void }> = ({ onClose, onSaved }) => { const toast = useToast(); const [clientes, setClientes] = useState([]); const [produtos, setProdutos] = useState([]); const [busy, setBusy] = useState(false); // origem do cliente: existente ou avulso const [clienteId, setClienteId] = useState(''); // '' = avulso const [cliNome, setCliNome] = useState(''); const [cliTipo, setCliTipo] = useState('clinica'); const [produtoId, setProdutoId] = useState(''); // '' = tipo livre const [tipo, setTipo] = useState(''); const [custo, setCusto] = useState(''); const [prazo, setPrazo] = useState(''); const [paciente, setPaciente] = useState(''); const [obs, setObs] = useState(''); useEffect(() => { HybridBackend.getLabClientes().then(r => setClientes(Array.isArray(r) ? r : [])).catch(() => {}); HybridBackend.getProteseProdutos().then(r => setProdutos(Array.isArray(r) ? r : [])).catch(() => {}); }, []); const salvar = async () => { if (!clienteId && !cliNome.trim()) { toast.error('INFORME O CLIENTE.'); return; } if (!produtoId && !tipo.trim()) { toast.error('INFORME O PRODUTO OU TIPO.'); return; } setBusy(true); try { const body: any = { paciente_nome: paciente.trim() || undefined, observacoes: obs.trim() || undefined, prazo_entrega: prazo || undefined }; if (clienteId) body.cliente_id = clienteId; else { body.cliente_nome = cliNome.trim(); body.cliente_tipo = cliTipo; } if (produtoId) body.produto_id = produtoId; else { body.tipo = tipo.trim(); body.custo = Number(custo) || 0; } const r = await HybridBackend.criarLabOs(body); if (r?.error) throw new Error(r.error); toast.success('ENTRADA REGISTRADA.'); onSaved(); } catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); } finally { setBusy(false); } }; const lbl = 'text-[10px] font-black text-gray-500 uppercase tracking-wide'; const inp = 'w-full mt-1 px-3 py-2 rounded-lg border border-gray-200 text-sm outline-none focus:border-teal-400'; return (
e.stopPropagation()}>

Dar entrada em OS

{/* Cliente */}
{!clienteId && (
setCliNome(e.target.value)} placeholder="Nome do cliente (clínica / dentista)" className={inp.replace('mt-1', '')} />
{CLI_TIPO.map(t => { const I = t.icon; return ( ); })}
)}
{/* Produto */}
{!produtoId && (
setTipo(e.target.value)} placeholder="Ex.: Coroa de Zircônia" className={inp.replace('mt-1', '') + ' flex-1'} /> setCusto(e.target.value)} type="number" step="0.01" placeholder="Custo" className={inp.replace('mt-1', '') + ' w-24'} />
)}
setPrazo(e.target.value)} type="date" className={inp} />
setPaciente(e.target.value)} placeholder="Nome" className={inp} />