533 lines
29 KiB
TypeScript
533 lines
29 KiB
TypeScript
import React, { useState, useEffect, useMemo } from 'react';
|
|
import {
|
|
Users,
|
|
Trash2,
|
|
Check,
|
|
Building2,
|
|
AlertCircle,
|
|
PlusCircle,
|
|
Camera,
|
|
Stethoscope,
|
|
ChevronRight
|
|
} from 'lucide-react';
|
|
import { PageHeader } from '../components/PageHeader.tsx';
|
|
import { HybridBackend } from '../services/backend.ts';
|
|
import { Paciente, Dentista, Procedimento, Especialidade } from '../types.ts';
|
|
import { useToast } from '../contexts/ToastContext.tsx';
|
|
import { create } from 'zustand';
|
|
|
|
// --- STORES ---
|
|
interface GTOItem {
|
|
id: string;
|
|
procedimentoId: string;
|
|
codigoTUSS: string;
|
|
codigoInterno: string;
|
|
descricao: string;
|
|
dente?: string;
|
|
face?: string;
|
|
arco?: string;
|
|
quantidade: number;
|
|
valorUnitario: number;
|
|
valorTotal: number;
|
|
observacao?: string;
|
|
}
|
|
|
|
interface GTOStore {
|
|
paciente: Paciente | null;
|
|
dentista: Dentista | null;
|
|
credenciada: string;
|
|
items: GTOItem[];
|
|
setPaciente: (p: Paciente | null) => void;
|
|
setDentista: (d: Dentista | null) => void;
|
|
setCredenciada: (c: string) => void;
|
|
addItem: (item: GTOItem) => void;
|
|
removeItem: (id: string) => void;
|
|
clear: () => void;
|
|
}
|
|
|
|
const useGTOStore = create<GTOStore>((set) => ({
|
|
paciente: null,
|
|
dentista: null,
|
|
credenciada: 'CASSEMS - SEDE CENTRAL',
|
|
items: [],
|
|
setPaciente: (paciente) => set({ paciente }),
|
|
setDentista: (dentista) => set({ dentista }),
|
|
setCredenciada: (credenciada) => set({ credenciada }),
|
|
addItem: (item) => set((state) => ({
|
|
items: [...state.items, item].slice(0, 20)
|
|
})),
|
|
removeItem: (id) => set((state) => ({
|
|
items: state.items.filter(i => i.id !== id)
|
|
})),
|
|
clear: () => set({ paciente: null, dentista: null, items: [] }),
|
|
}));
|
|
|
|
// --- ODONTOGRAM DATA (4x2 Grid Layout) ---
|
|
const DENTES_ADULTO = {
|
|
q1: [14, 13, 12, 11, 18, 17, 16, 15], // Top: 14-11, Bottom: 18-15
|
|
q2: [21, 22, 23, 24, 25, 26, 27, 28], // Top: 21-24, Bottom: 25-28
|
|
q4: [44, 43, 42, 41, 48, 47, 46, 45], // Top: 44-41, Bottom: 48-45
|
|
q3: [31, 32, 33, 34, 35, 36, 37, 38], // Top: 31-34, Bottom: 35-38
|
|
extras: { q1: 19, q2: 29, q4: 49, q3: 39 }
|
|
};
|
|
|
|
const DENTES_CRIANCA = {
|
|
// Balanced 4x2 distribution with nulls for spacing (matching image layout)
|
|
q1: [null, 53, 52, 51, null, null, 55, 54],
|
|
q2: [61, 62, 63, null, 64, 65, null, null],
|
|
q4: [null, 43, 42, 41, null, null, 46, 45],
|
|
q3: [71, 72, 73, null, 74, 75, null, null]
|
|
};
|
|
|
|
// --- COMPONENT ---
|
|
export const LancarGTO: React.FC = () => {
|
|
const store = useGTOStore();
|
|
const toast = useToast();
|
|
|
|
const [pacientes, setPacientes] = useState<Paciente[]>([]);
|
|
const [dentistas, setDentistas] = useState<Dentista[]>([]);
|
|
const [especialidades, setEspecialidades] = useState<Especialidade[]>([]);
|
|
const [procedimentos, setProcedimentos] = useState<Procedimento[]>([]);
|
|
|
|
const [selectedEsp, setSelectedEsp] = useState<string>('');
|
|
const [selectedProc, setSelectedProc] = useState<Procedimento | null>(null);
|
|
const [pacienteTipo, setPacienteTipo] = useState<'ADULTO' | 'CRIANCA'>('ADULTO');
|
|
const [selectedDentes, setSelectedDentes] = useState<string[]>([]);
|
|
const [selectedArco, setSelectedArco] = useState<'AI' | 'AS' | null>(null);
|
|
const [face, setFace] = useState('');
|
|
const [obs, setObs] = useState('');
|
|
|
|
useEffect(() => {
|
|
const load = async () => {
|
|
const p = await HybridBackend.getPacientes();
|
|
const d = await HybridBackend.getDentistas();
|
|
const es = await HybridBackend.getEspecialidades();
|
|
const pr = await HybridBackend.getProcedimentos();
|
|
setPacientes(p);
|
|
setDentistas(d);
|
|
setEspecialidades(es);
|
|
setProcedimentos(pr);
|
|
};
|
|
load();
|
|
}, []);
|
|
|
|
const filteredProcedimentos = useMemo(() => {
|
|
if (!selectedEsp) return [];
|
|
return procedimentos.filter(p => p.especialidadeId === selectedEsp);
|
|
}, [selectedEsp, procedimentos]);
|
|
|
|
const toggleDente = (num: string) => {
|
|
if (selectedProc?.tipo_regiao !== 'DENTE') {
|
|
toast.error("ESTE PROCEDIMENTO NÃO PERMITE SELEÇÃO DE DENTE INDIVIDUAL.");
|
|
return;
|
|
}
|
|
setSelectedArco(null);
|
|
setSelectedDentes(prev =>
|
|
prev.includes(num) ? prev.filter(d => d !== num) : [...prev, num]
|
|
);
|
|
};
|
|
|
|
const selectArco = (arco: 'AI' | 'AS') => {
|
|
if (selectedProc?.tipo_regiao !== 'ARCO') {
|
|
toast.error("ESTE PROCEDIMENTO NÃO PERMITE SELEÇÃO DE ARCO.");
|
|
return;
|
|
}
|
|
setSelectedDentes([]);
|
|
setSelectedArco(arco);
|
|
};
|
|
|
|
const handleAddItem = () => {
|
|
if (!selectedProc) {
|
|
toast.error("SELECIONE UM PROCEDIMENTO PRIMEIRO.");
|
|
return;
|
|
}
|
|
|
|
// Validação de Região
|
|
if (selectedProc.tipo_regiao === 'DENTE' && selectedDentes.length === 0) {
|
|
toast.error("ESTE PROCEDIMENTO EXIGE A SELEÇÃO DE AO MENOS UM DENTE.");
|
|
return;
|
|
}
|
|
|
|
if (selectedProc.tipo_regiao === 'ARCO' && !selectedArco) {
|
|
toast.error("ESTE PROCEDIMENTO EXIGE A SELEÇÃO DE UM ARCO (AI/AS).");
|
|
return;
|
|
}
|
|
|
|
// Validação de Face
|
|
if (selectedProc.exige_face && (!face || face.trim() === '')) {
|
|
toast.error("A FACE É OBRIGATÓRIA PARA ESTE PROCEDIMENTO.");
|
|
return;
|
|
}
|
|
|
|
const newItem: GTOItem = {
|
|
id: Math.random().toString(36).substring(7),
|
|
procedimentoId: selectedProc.id,
|
|
codigoTUSS: selectedProc.codigo || '000000',
|
|
codigoInterno: selectedProc.codigoInterno || '',
|
|
descricao: selectedProc.nome,
|
|
dente: selectedDentes.join(', '),
|
|
arco: selectedArco || undefined,
|
|
face: face,
|
|
quantidade: 1,
|
|
valorUnitario: selectedProc.valorParticular || 0,
|
|
valorTotal: selectedProc.valorParticular || 0,
|
|
observacao: obs,
|
|
};
|
|
|
|
store.addItem(newItem);
|
|
toast.success("ADICIONADO AO RASCUNHO.");
|
|
|
|
// Reset selection fields
|
|
setSelectedDentes([]);
|
|
setSelectedArco(null);
|
|
setFace('');
|
|
setObs('');
|
|
};
|
|
|
|
const handleFinalize = async () => {
|
|
if (!store.paciente || !store.dentista || store.items.length === 0) {
|
|
toast.error("PACIENTE, DENTISTA E PELO MENOS 1 ITEM SÃO OBRIGATÓRIOS.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
toast.success("GTO GERADA COM SUCESSO!");
|
|
store.clear();
|
|
setSelectedEsp('');
|
|
setSelectedProc(null);
|
|
} catch {
|
|
toast.error("ERRO AO GERAR GTO.");
|
|
}
|
|
};
|
|
|
|
const totalGeral = store.items.reduce((sum, i) => sum + i.valorTotal, 0);
|
|
|
|
const renderDenteBtn = (num: number | string | null, keyIdx?: number) => {
|
|
if (num === null) return <div key={`empty-${keyIdx}`} className="w-9 h-9" />;
|
|
const sStr = num.toString();
|
|
const isSelected = selectedDentes.includes(sStr);
|
|
const isDisabled = !!selectedProc && selectedProc.tipo_regiao !== 'DENTE';
|
|
|
|
return (
|
|
<button
|
|
key={sStr}
|
|
onClick={() => toggleDente(sStr)}
|
|
disabled={isDisabled}
|
|
className={`w-9 h-9 rounded-lg border-2 text-[11px] font-black transition-all shadow-sm flex items-center justify-center
|
|
${isSelected
|
|
? 'bg-blue-600 border-blue-700 text-white scale-110 z-10'
|
|
: isDisabled
|
|
? 'bg-gray-50 border-gray-100 text-gray-300 cursor-not-allowed opacity-50'
|
|
: 'bg-white border-gray-200 text-gray-700 hover:border-blue-400 hover:bg-blue-50'}
|
|
`}
|
|
>
|
|
{sStr}
|
|
</button>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<PageHeader
|
|
title="LANÇAR GTO"
|
|
description="MONTGEM DINÂMICA DE GUIA ODONTOLÓGICA (GTO)."
|
|
/>
|
|
|
|
<div className="grid grid-cols-12 gap-6 items-start">
|
|
|
|
{/* LEFT PANEL: ODONTOGRAM & ITEMS */}
|
|
<div className="col-span-12 lg:col-span-8 space-y-6">
|
|
|
|
{/* ODONTOGRAM BOX */}
|
|
<div className="bg-white rounded-3xl border border-gray-100 shadow-xl p-8 space-y-6">
|
|
<div className="flex justify-center mb-4">
|
|
<div className="inline-flex bg-gray-100 p-1 rounded-xl shadow-inner">
|
|
<button
|
|
onClick={() => { setPacienteTipo('ADULTO'); setSelectedDentes([]); }}
|
|
className={`px-8 py-2 rounded-lg text-xs font-black transition-all uppercase ${pacienteTipo === 'ADULTO' ? 'bg-blue-600 text-white shadow-md scale-105' : 'text-gray-500 hover:text-gray-800'}`}
|
|
>
|
|
Adulto
|
|
</button>
|
|
<button
|
|
onClick={() => { setPacienteTipo('CRIANCA'); setSelectedDentes([]); }}
|
|
className={`px-8 py-2 rounded-lg text-xs font-black transition-all uppercase ${pacienteTipo === 'CRIANCA' ? 'bg-blue-600 text-white shadow-md scale-105' : 'text-gray-500 hover:text-gray-800'}`}
|
|
>
|
|
Criança
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Dynamic Odontogram Grid */}
|
|
<div className="relative border-t border-b border-gray-50 py-10">
|
|
{/* Visual Separators */}
|
|
<div className="absolute left-1/2 top-4 bottom-4 w-[2px] bg-gray-200 -translate-x-1/2"></div>
|
|
<div className="absolute top-1/2 left-4 right-4 h-[2px] bg-gray-200 -translate-y-1/2"></div>
|
|
|
|
<div className="grid grid-cols-2 gap-x-12 gap-y-16">
|
|
{/* Q1 & Q2 (SUPERIOR) */}
|
|
<div className="flex justify-end items-center gap-2 pr-2">
|
|
{pacienteTipo === 'ADULTO' && renderDenteBtn(DENTES_ADULTO.extras.q1)}
|
|
<div className="grid grid-cols-4 gap-2">
|
|
{(pacienteTipo === 'ADULTO' ? DENTES_ADULTO.q1 : DENTES_CRIANCA.q1).map((d, i) => renderDenteBtn(d, i))}
|
|
</div>
|
|
</div>
|
|
<div className="flex justify-start items-center gap-2 pl-2">
|
|
<div className="grid grid-cols-4 gap-2">
|
|
{(pacienteTipo === 'ADULTO' ? DENTES_ADULTO.q2 : DENTES_CRIANCA.q2).map((d, i) => renderDenteBtn(d, i))}
|
|
</div>
|
|
{pacienteTipo === 'ADULTO' && renderDenteBtn(DENTES_ADULTO.extras.q2)}
|
|
</div>
|
|
|
|
{/* Q4 & Q3 (INFERIOR) */}
|
|
<div className="flex justify-end items-center gap-2 pr-2">
|
|
{pacienteTipo === 'ADULTO' && renderDenteBtn(DENTES_ADULTO.extras.q4)}
|
|
<div className="grid grid-cols-4 gap-2">
|
|
{(pacienteTipo === 'ADULTO' ? DENTES_ADULTO.q4 : DENTES_CRIANCA.q4).map((d, i) => renderDenteBtn(d, i))}
|
|
</div>
|
|
</div>
|
|
<div className="flex justify-start items-center gap-2 pl-2">
|
|
<div className="grid grid-cols-4 gap-2">
|
|
{(pacienteTipo === 'ADULTO' ? DENTES_ADULTO.q3 : DENTES_CRIANCA.q3).map((d, i) => renderDenteBtn(d, i))}
|
|
</div>
|
|
{pacienteTipo === 'ADULTO' && renderDenteBtn(DENTES_ADULTO.extras.q3)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-center gap-4 pt-4">
|
|
<button
|
|
onClick={() => selectArco('AI')}
|
|
disabled={!!selectedProc && selectedProc.tipo_regiao !== 'ARCO'}
|
|
className={`px-10 py-3 rounded-2xl border-2 font-black text-sm uppercase transition-all shadow-sm
|
|
${selectedArco === 'AI' ? 'bg-amber-500 border-amber-600 text-white' :
|
|
(!!selectedProc && selectedProc.tipo_regiao !== 'ARCO') ? 'bg-gray-50 border-gray-100 text-gray-300 cursor-not-allowed opacity-50' :
|
|
'bg-white border-gray-100 text-gray-500 hover:border-amber-400'}`}
|
|
>
|
|
AI - Inferior
|
|
</button>
|
|
<button
|
|
onClick={() => selectArco('AS')}
|
|
disabled={!!selectedProc && selectedProc.tipo_regiao !== 'ARCO'}
|
|
className={`px-10 py-3 rounded-2xl border-2 font-black text-sm uppercase transition-all shadow-sm
|
|
${selectedArco === 'AS' ? 'bg-amber-500 border-amber-600 text-white' :
|
|
(!!selectedProc && selectedProc.tipo_regiao !== 'ARCO') ? 'bg-gray-50 border-gray-100 text-gray-300 cursor-not-allowed opacity-50' :
|
|
'bg-white border-gray-100 text-gray-500 hover:border-amber-400'}`}
|
|
>
|
|
AS - Superior
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* RASCUNHO LIST */}
|
|
<div className="bg-white rounded-3xl border border-gray-100 overflow-hidden shadow-xl">
|
|
<div className="bg-[#d49a4a] p-4 text-white font-black text-sm uppercase flex justify-between items-center tracking-widest">
|
|
<span>PROCEDIMENTOS DO RASCUNHO</span>
|
|
<div className="bg-white/20 px-3 py-1 rounded-full text-[10px]">{store.items.length}/20 ITENS</div>
|
|
</div>
|
|
|
|
<div className="p-6 space-y-4">
|
|
{store.items.length === 0 && (
|
|
<div className="py-16 text-center text-gray-300 font-bold uppercase flex flex-col items-center gap-4">
|
|
<div className="w-16 h-16 bg-gray-50 rounded-full flex items-center justify-center outline outline-gray-100 outline-offset-4">
|
|
<AlertCircle size={32} />
|
|
</div>
|
|
NÃO HÁ ITENS ADICIONADOS
|
|
</div>
|
|
)}
|
|
|
|
{store.items.map((item) => (
|
|
<div key={item.id} className="border border-gray-100 rounded-2xl p-5 bg-gray-50/30 hover:bg-white transition-all shadow-sm group">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-3">
|
|
<span className="bg-blue-600 text-white text-[10px] font-black px-2 py-1 rounded-md uppercase shadow-sm">COD {item.codigoTUSS}</span>
|
|
<h4 className="font-black text-gray-900 text-sm uppercase tracking-tight">{item.descricao}</h4>
|
|
</div>
|
|
<div className="flex items-center gap-2 text-[10px] text-gray-400 font-bold uppercase">
|
|
<Users size={12} /> {store.paciente?.nome}
|
|
</div>
|
|
</div>
|
|
<button onClick={() => store.removeItem(item.id)} className="text-gray-300 hover:text-red-600 p-2 transition-colors">
|
|
<Trash2 size={20} />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-4 gap-6 text-[11px] font-black text-gray-500 uppercase tracking-wider mb-4 border-t border-gray-100 pt-4">
|
|
<div>DENTE: <span className="text-blue-600 ml-1">{item.dente || '--'}</span></div>
|
|
<div>ARCO: <span className="text-amber-600 ml-1">{item.arco || '--'}</span></div>
|
|
<div>FACE: <span className="text-gray-900 ml-1">{item.face || '--'}</span></div>
|
|
<div>VALOR: <span className="text-green-600 ml-1 text-xs">R$ {item.valorTotal.toFixed(2)}</span></div>
|
|
</div>
|
|
|
|
{item.observacao && (
|
|
<div className="text-[11px] text-gray-500 bg-white p-3 border border-gray-100 rounded-xl leading-relaxed">
|
|
<span className="font-black text-gray-400 uppercase mr-2 opacity-50">OBS:</span> {item.observacao}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
|
|
<div className="pt-6 border-t-2 border-dotted border-gray-200 flex justify-between items-center">
|
|
<button className="flex items-center gap-2 text-blue-600 font-black text-[11px] uppercase hover:bg-blue-50 px-4 py-2 rounded-xl transition-all">
|
|
<Camera size={16} /> ANEXAR DOCUMENTOS
|
|
</button>
|
|
<div className="text-right">
|
|
<p className="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] mb-1">TOTAL ACUMULADO</p>
|
|
<h3 className="text-3xl font-black text-gray-900 p-2 bg-gray-50 rounded-2xl border border-gray-100 shadow-inner">R$ {totalGeral.toFixed(2)}</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* RIGHT PANEL: WORKFLOW SELECTORS */}
|
|
<div className="col-span-12 lg:col-span-4 space-y-4 sticky top-6">
|
|
|
|
{/* STEP 1: PACIENTE */}
|
|
<div className="bg-white p-6 rounded-3xl border border-gray-100 shadow-xl space-y-4">
|
|
<div className="flex items-center gap-3 text-blue-600 font-black text-base uppercase tracking-wider">
|
|
<Users size={20} strokeWidth={3} /> 01. Paciente
|
|
</div>
|
|
<select
|
|
value={store.paciente?.id || ''}
|
|
onChange={(e) => {
|
|
const p = pacientes.find(p => p.id === e.target.value);
|
|
store.setPaciente(p || null);
|
|
}}
|
|
className="w-full p-4 bg-gray-50 border-2 border-gray-100 rounded-2xl font-black text-gray-800 text-sm focus:border-blue-500 outline-none transition-all uppercase"
|
|
>
|
|
<option value="">SELECIONE O BENEFICIÁRIO</option>
|
|
{pacientes.map(p => <option key={p.id} value={p.id}>{p.nome}</option>)}
|
|
</select>
|
|
</div>
|
|
|
|
{/* STEP 2: ESPECIALIDADE & PROCEDIMENTO */}
|
|
<div className="bg-white p-6 rounded-3xl border border-gray-100 shadow-xl space-y-5">
|
|
<div className="flex items-center gap-3 text-orange-500 font-black text-base uppercase tracking-wider">
|
|
<Stethoscope size={20} strokeWidth={3} /> 02. Especialidade
|
|
</div>
|
|
<div className="space-y-3">
|
|
<select
|
|
value={selectedEsp}
|
|
onChange={(e) => {
|
|
setSelectedEsp(e.target.value);
|
|
setSelectedProc(null);
|
|
}}
|
|
className="w-full p-4 bg-gray-50 border-2 border-gray-100 rounded-2xl font-black text-gray-700 text-sm focus:border-orange-400 outline-none transition-all uppercase"
|
|
>
|
|
<option value="">ESCOLHA A ÁREA...</option>
|
|
{especialidades.map(es => <option key={es.id} value={es.id}>{es.nome}</option>)}
|
|
</select>
|
|
|
|
{selectedEsp && (
|
|
<div className="space-y-2">
|
|
<select
|
|
value={selectedProc?.id || ''}
|
|
onChange={(e) => {
|
|
const pr = procedimentos.find(p => p.id === e.target.value);
|
|
setSelectedProc(pr || null);
|
|
setSelectedDentes([]);
|
|
setSelectedArco(null);
|
|
}}
|
|
className="w-full p-4 bg-white border-2 border-orange-100 rounded-2xl font-black text-gray-800 text-sm shadow-inner uppercase"
|
|
>
|
|
<option value="">BUSCAR PROCEDIMENTO</option>
|
|
{filteredProcedimentos.map(p => <option key={p.id} value={p.id}>{p.nome}</option>)}
|
|
</select>
|
|
|
|
{selectedProc && (
|
|
<div className="bg-orange-50 p-3 rounded-xl border border-orange-100 animate-in fade-in slide-in-from-top-1 duration-300">
|
|
<p className="text-[9px] font-black text-orange-600 uppercase mb-1">REGRAS DE SELEÇÃO</p>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="text-[10px] font-bold text-orange-800 flex items-center gap-1">
|
|
<ChevronRight size={10} />
|
|
{selectedProc.tipo_regiao === 'DENTE' ? 'EXIGE DENTE(S)' : selectedProc.tipo_regiao === 'ARCO' ? 'EXIGE ARCO (AI/AS)' : 'PROCEDIMENTO GERAL'}
|
|
</span>
|
|
{selectedProc.exige_face && (
|
|
<span className="text-[10px] font-bold text-red-600 flex items-center gap-1">
|
|
<ChevronRight size={10} /> EXIGE FACE CLINICA
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* STEP 3: DENTISTA */}
|
|
<div className="bg-white p-6 rounded-3xl border border-gray-100 shadow-xl space-y-4">
|
|
<div className="flex items-center gap-3 text-[#2d6a4f] font-black text-base uppercase tracking-wider">
|
|
<Check size={20} strokeWidth={3} className="bg-green-100 p-1 rounded-full text-green-700" /> 03. Dentista Executor
|
|
</div>
|
|
<select
|
|
value={store.dentista?.id || ''}
|
|
onChange={(e) => {
|
|
const d = dentistas.find(d => d.id === e.target.value);
|
|
store.setDentista(d || null);
|
|
}}
|
|
className="w-full p-4 bg-gray-50 border-2 border-gray-100 rounded-2xl font-black text-gray-800 text-sm focus:border-green-500 outline-none transition-all uppercase"
|
|
>
|
|
<option value="">SELECIONE O PROFISSIONAL</option>
|
|
{dentistas.map(d => <option key={d.id} value={d.id}>{d.nome}</option>)}
|
|
</select>
|
|
</div>
|
|
|
|
{/* STEP 4: DETALHES (FACE/OBS) */}
|
|
<div className="bg-white p-6 rounded-3xl border border-gray-100 shadow-xl space-y-4">
|
|
<div className="grid grid-cols-1 gap-4">
|
|
<div>
|
|
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 flex justify-between">
|
|
<span>Face (O, M, D, V, L, P)</span>
|
|
{selectedProc?.exige_face && <span className="text-red-500 font-black animate-pulse">* OBRIGATÓRIO</span>}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={face}
|
|
onChange={e => setFace(e.target.value.toUpperCase())}
|
|
disabled={selectedProc?.tipo_regiao === 'GERAL' && !selectedProc?.exige_face}
|
|
className={`w-full p-4 border-2 rounded-2xl text-sm font-black outline-none transition-all
|
|
${selectedProc?.exige_face ? 'bg-red-50 border-red-100 focus:border-red-400' : 'bg-gray-50 border-gray-100 focus:border-amber-400'}
|
|
`}
|
|
placeholder={selectedProc?.exige_face ? "DIGITE AS FACES..." : "OPCIONAL"}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 italic">Observações Clínicas</label>
|
|
<div className="relative">
|
|
<textarea
|
|
value={obs}
|
|
onChange={e => setObs(e.target.value)}
|
|
className="w-full p-4 bg-gray-50 border-2 border-gray-100 rounded-2xl text-[11px] h-20 focus:border-amber-400 outline-none transition-all"
|
|
/>
|
|
<PlusCircle size={24} className="absolute bottom-4 right-4 text-green-500 opacity-50" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ACTIONS */}
|
|
<div className="grid grid-cols-4 gap-4 p-2">
|
|
<button
|
|
onClick={handleAddItem}
|
|
className="col-span-3 bg-[#2d6a4f] text-white py-5 rounded-3xl font-black text-lg uppercase shadow-2xl shadow-green-200 hover:bg-[#1b4332] active:scale-95 transition-all flex items-center justify-center gap-3 group"
|
|
>
|
|
ADICIONAR ITEM <ChevronRight className="group-hover:translate-x-1 transition-transform" />
|
|
</button>
|
|
<button
|
|
onClick={handleFinalize}
|
|
className="col-span-1 bg-gray-900 text-white py-5 rounded-3xl flex items-center justify-center shadow-xl hover:bg-black active:scale-95 transition-all"
|
|
title="FINALIZAR GTO"
|
|
>
|
|
<Check size={32} strokeWidth={4} />
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|