import React, { useState, useEffect, useCallback } from 'react'; import { FlaskConical, Plus, X, Search, Clock, AlertTriangle, ImagePlus, ChevronRight, Trash2, RefreshCw, ShieldCheck, Tags, Pencil, Check, Package, Wallet, Camera, CheckCircle2, Circle, History, Printer, MapPin, Building2, Share2, FileText, Star } from 'lucide-react'; import { HybridBackend } from '../services/backend.ts'; import { useToast } from '../contexts/ToastContext.tsx'; import { useConfirm } from '../contexts/ConfirmContext.tsx'; import { PageHeader } from '../components/PageHeader.tsx'; const fmtBRL = (v: number) => 'R$ ' + Number(v || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 }); const dataBR = (d: string) => (d || '').slice(0, 10).split('-').reverse().join('/'); const dataHoraBR = (d: string) => { if (!d) return ''; const dt = new Date(d); return isNaN(dt.getTime()) ? dataBR(d) : dt.toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); }; const STATUS_LABEL: Record = { solicitado: 'Solicitado', recebido: 'Recebido', producao: 'Em produção', prova: 'Prova', ajuste: 'Ajuste', finalizado: 'Finalizado', entregue: 'Entregue', cancelado: 'Cancelado', }; const STATUS_COR: Record = { solicitado: 'bg-gray-100 text-gray-600', recebido: 'bg-blue-100 text-blue-700', producao: 'bg-amber-100 text-amber-700', prova: 'bg-indigo-100 text-indigo-700', ajuste: 'bg-orange-100 text-orange-700', finalizado: 'bg-teal-100 text-teal-700', entregue: 'bg-green-100 text-green-700', cancelado: 'bg-red-100 text-red-600', }; const Badge: React.FC<{ s: string }> = ({ s }) => ( {STATUS_LABEL[s] || s} ); // ── Modal: Nova OS (lado clínica) ─────────────────────────────────────────── const NovaOSModal: React.FC<{ onClose: () => void; onSaved: () => void }> = ({ onClose, onSaved }) => { const toast = useToast(); const [labs, setLabs] = useState([]); const [dentistas, setDentistas] = useState([]); const [busca, setBusca] = useState(''); const [pacientes, setPacientes] = useState([]); const [paciente, setPaciente] = useState(null); const [produtos, setProdutos] = useState([]); const [form, setForm] = useState({ protetico_id: '', produto_id: '', tipo: '', dentista_id: '', material: '', cor: '', dentes: '', prazo_entrega: '', valor: '', observacoes: '' }); const [saving, setSaving] = useState(false); const ws = HybridBackend.getActiveWorkspace(); useEffect(() => { HybridBackend.getProteseLaboratorios().then(r => setLabs(Array.isArray(r) ? r : [])).catch(() => setLabs([])); HybridBackend.getDentistas(ws?.id).then(r => setDentistas(Array.isArray(r) ? r : [])).catch(() => setDentistas([])); }, [ws?.id]); // Catálogo (preço fixo) do laboratório escolhido. useEffect(() => { if (!form.protetico_id) { setProdutos([]); return; } HybridBackend.getLaboratorioProdutos(form.protetico_id) .then(r => setProdutos(Array.isArray(r) ? r : [])) .catch(() => setProdutos([])); setForm((f: any) => ({ ...f, produto_id: '', tipo: '' })); }, [form.protetico_id]); const produtoSel = produtos.find(p => p.id === form.produto_id); useEffect(() => { if (paciente) return; const t = setTimeout(() => { HybridBackend.getPacientes(busca).then(r => setPacientes(r.data || [])).catch(() => setPacientes([])); }, 250); return () => clearTimeout(t); }, [busca, paciente]); const set = (k: string, v: any) => setForm((f: any) => ({ ...f, [k]: v })); const salvar = async () => { if (!form.protetico_id) { toast.error('SELECIONE O LABORATÓRIO.'); return; } if (!form.produto_id && !form.tipo) { toast.error('SELECIONE O PRODUTO.'); return; } const dent = dentistas.find(d => d.id === form.dentista_id); setSaving(true); try { await HybridBackend.createProteseOS({ ...form, valor: Number(form.valor) || 0, paciente_id: paciente?.id || null, paciente_nome: paciente?.nome || null, dentista_nome: dent?.nome || null, }); toast.success('OS DE PRÓTESE CRIADA.'); onSaved(); } catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); } finally { setSaving(false); } }; return (
e.stopPropagation()}>

Nova OS de Prótese

{/* Paciente */}
{paciente ? (
{paciente.nome}
) : (
setBusca(e.target.value)} placeholder="Buscar paciente..." className="w-full pl-9 pr-3 py-2 rounded-xl border border-gray-200 text-sm" /> {busca && pacientes.length > 0 && (
{pacientes.slice(0, 8).map(p => ( ))}
)}
)}
{/* Laboratório */}
{labs.length === 0 &&

Nenhum protético com vínculo ou no diretório. Convide um na Equipe ou no marketplace de Profissionais.

}
{/* Produto (catálogo do laboratório — preço fixo) */}
{form.protetico_id && produtos.length > 0 ? ( ) : ( set('tipo', e.target.value)} placeholder={form.protetico_id ? 'Sem catálogo — descreva o trabalho' : 'Selecione o laboratório primeiro'} disabled={!form.protetico_id} className="w-full mt-1 px-3 py-2 rounded-xl border border-gray-200 text-sm disabled:bg-gray-50" /> )} {produtoSel && (

Custo de laboratório {fmtBRL(produtoSel.preco)} · garantia {produtoSel.garantia_meses} meses

)}
{/* Dentista */}
{/* Material + cor + dentes */}
set('material', e.target.value)} placeholder="Zircônia..." className="w-full mt-1 px-3 py-2 rounded-xl border border-gray-200 text-sm" />
set('cor', e.target.value)} placeholder="A2..." className="w-full mt-1 px-3 py-2 rounded-xl border border-gray-200 text-sm" />
set('dentes', e.target.value)} placeholder="11,21" className="w-full mt-1 px-3 py-2 rounded-xl border border-gray-200 text-sm" />
{/* Prazo + valor cobrado do paciente */}
set('prazo_entrega', e.target.value)} className="w-full mt-1 px-3 py-2 rounded-xl border border-gray-200 text-sm" />
set('valor', e.target.value)} placeholder="0,00" className="w-full mt-1 px-3 py-2 rounded-xl border border-gray-200 text-sm" />