feat(protese): seletor de protético em modal (busca, favorito/padrão, vincular por e-mail)
O dropdown só listava protéticos internos (vinculados) ou do diretório público — um protético com conta fora do diretório (ex.: claudemir) não aparecia para a clínica. Reformulado: - Componente ProteticoPicker (modal): busca por nome/e-mail; estrela de FAVORITO; marcar PADRÃO (pré-selecionado na próxima OS/GTO, só um por clínica); e "Adicionar por e-mail", que VINCULA um protético existente como interno — resolve o caso de não estar no diretório, sem expô-lo publicamente. - Backend: tabela protese_protetico_pref (favorito/padrão por clínica); GET /protese/laboratorios passa a retornar favorito/padrao e ordena padrão→favorito→interno→nota→nome; POST .../:id/pref e POST /protese/laboratorios/vincular. - Picker aplicado no LancarGTO (avaliação/GTO) e na NovaOSModal; ProteticoInternoInline (protético SEM conta) mantido ao lado. Validado em DEV na CONSULTT CLINIC: claudemir ausente → vinculado por e-mail → aparece interno e, como favorito+padrão, no topo da lista. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
X
|
||||
} from 'lucide-react';
|
||||
import { PageHeader } from '../components/PageHeader.tsx';
|
||||
import { ProteticoPicker } from '../components/ProteticoPicker.tsx';
|
||||
import { HybridBackend } from '../services/backend.ts';
|
||||
import { Paciente, Dentista, Procedimento, Especialidade } from '../types.ts';
|
||||
import { useToast } from '../contexts/ToastContext.tsx';
|
||||
@@ -123,7 +124,6 @@ export const LancarGTO: React.FC = () => {
|
||||
const [patientSearchTerm, setPatientSearchTerm] = useState('');
|
||||
|
||||
// --- PRÓTESE (Fase 1): catálogo do protético entra como "procedimentos" do GTO ---
|
||||
const [laboratorios, setLaboratorios] = useState<any[]>([]);
|
||||
const [selectedLab, setSelectedLab] = useState<string>('');
|
||||
const [labProdutos, setLabProdutos] = useState<any[]>([]);
|
||||
const [selectedProdutoId, setSelectedProdutoId] = useState<string>('');
|
||||
@@ -162,12 +162,6 @@ export const LancarGTO: React.FC = () => {
|
||||
[labProdutos, selectedProdutoId]
|
||||
);
|
||||
|
||||
// Carrega os laboratórios acessíveis assim que a especialidade de prótese é escolhida.
|
||||
useEffect(() => {
|
||||
if (!isProtese || laboratorios.length) return;
|
||||
HybridBackend.getProteseLaboratorios().then(l => setLaboratorios(Array.isArray(l) ? l : [])).catch(() => {});
|
||||
}, [isProtese, laboratorios.length]);
|
||||
|
||||
// Catálogo do laboratório selecionado.
|
||||
useEffect(() => {
|
||||
if (!selectedLab) { setLabProdutos([]); return; }
|
||||
@@ -482,14 +476,7 @@ export const LancarGTO: React.FC = () => {
|
||||
|
||||
{selectedEsp && isProtese && (
|
||||
<div className="space-y-3">
|
||||
<select
|
||||
value={selectedLab}
|
||||
onChange={(e) => { setSelectedLab(e.target.value); setSelectedProdutoId(''); setProtValor(''); }}
|
||||
className="w-full p-4 bg-gray-50 border-2 border-gray-100 rounded-2xl font-black text-gray-700 text-sm focus:border-teal-400 outline-none transition-all uppercase"
|
||||
>
|
||||
<option value="">ESCOLHA O LABORATÓRIO / PROTÉTICO...</option>
|
||||
{laboratorios.map(l => <option key={l.id} value={l.id}>{l.nome}{l.interno ? ' (INTERNO)' : ''}{l.nota != null ? ` · ★ ${l.nota}` : ''}</option>)}
|
||||
</select>
|
||||
<ProteticoPicker value={selectedLab} onChange={(id) => { setSelectedLab(id); setSelectedProdutoId(''); setProtValor(''); }} />
|
||||
|
||||
{selectedLab && (labProdutos.length === 0 ? (
|
||||
<div className="bg-amber-50 border-2 border-amber-100 rounded-2xl p-4 text-center">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useToast } from '../contexts/ToastContext.tsx';
|
||||
import { useConfirm } from '../contexts/ConfirmContext.tsx';
|
||||
import { PageHeader } from '../components/PageHeader.tsx';
|
||||
import { rotulosPaciente } from '../utils/pacienteLabel.ts';
|
||||
import { ProteticoPicker } from '../components/ProteticoPicker.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('/');
|
||||
@@ -63,7 +64,7 @@ const ProteticoInternoInline: React.FC<{ onCriado: (id: string) => void }> = ({
|
||||
// ── Modal: Nova OS (lado clínica) ───────────────────────────────────────────
|
||||
const NovaOSModal: React.FC<{ onClose: () => void; onSaved: () => void }> = ({ onClose, onSaved }) => {
|
||||
const toast = useToast();
|
||||
const [labs, setLabs] = useState<any[]>([]);
|
||||
const [pickerKey, setPickerKey] = useState(0);
|
||||
const [dentistas, setDentistas] = useState<any[]>([]);
|
||||
const [busca, setBusca] = useState('');
|
||||
const [pacientes, setPacientes] = useState<any[]>([]);
|
||||
@@ -74,7 +75,6 @@ const NovaOSModal: React.FC<{ onClose: () => void; onSaved: () => void }> = ({ o
|
||||
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]);
|
||||
|
||||
@@ -152,11 +152,8 @@ const NovaOSModal: React.FC<{ onClose: () => void; onSaved: () => void }> = ({ o
|
||||
{/* Laboratório */}
|
||||
<div>
|
||||
<label className="text-[10px] font-black text-gray-500 uppercase">Laboratório / Protético *</label>
|
||||
<select value={form.protetico_id} onChange={e => set('protetico_id', e.target.value)} className="w-full mt-1 px-3 py-2 rounded-xl border border-gray-200 text-sm">
|
||||
<option value="">Selecione...</option>
|
||||
{labs.map(l => <option key={l.id} value={l.id}>{l.nome}{l.interno ? ' (interno)' : ''}{l.nota != null ? ` · ★ ${l.nota}` : ''}</option>)}
|
||||
</select>
|
||||
<ProteticoInternoInline onCriado={(id) => { HybridBackend.getProteseLaboratorios().then(r => { setLabs(Array.isArray(r) ? r : []); set('protetico_id', id); }); }} />
|
||||
<div className="mt-1"><ProteticoPicker key={pickerKey} value={form.protetico_id} onChange={(id) => set('protetico_id', id)} /></div>
|
||||
<ProteticoInternoInline onCriado={(id) => { set('protetico_id', id); setPickerKey(k => k + 1); }} />
|
||||
</div>
|
||||
{/* Produto (catálogo do laboratório — preço fixo) */}
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user