feat(admin-procedimentos): num_faces (dentística) e num_raizes (endodontia) + oculta ARCO

- Dropdown 1-5 de faces visível quando especialidade é Dentística
- Dropdown 1-5 de raízes visível quando especialidade é Endodontia
- Opção ARCO COMPLETO oculta nos dois casos (não se aplica)
- Select de especialidade agora controlado — reseta num_faces/num_raizes ao trocar
- DB: ALTER TABLE procedimentos ADD COLUMN num_faces INTEGER, num_raizes INTEGER (executado)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-05-15 05:20:55 +02:00
parent ed6631f9ce
commit 8fad5a2529
2 changed files with 66 additions and 3 deletions
+2
View File
@@ -242,6 +242,8 @@ export interface Procedimento {
codigoInterno?: string; codigoInterno?: string;
descricao?: string; descricao?: string;
descricao_mobile?: string; descricao_mobile?: string;
num_faces?: number;
num_raizes?: number;
especialidadeId: string; especialidadeId: string;
especialidadeNome: string; especialidadeNome: string;
valorParticular: number; valorParticular: number;
+64 -3
View File
@@ -463,6 +463,8 @@ export const ProcedimentosView: React.FC = () => {
valorParticular: parseFloat(data.valorParticular), valorParticular: parseFloat(data.valorParticular),
tipo_regiao: editingProcedimento?.tipo_regiao || 'GERAL', tipo_regiao: editingProcedimento?.tipo_regiao || 'GERAL',
exige_face: editingProcedimento?.tipo_regiao === 'DENTE' ? !!editingProcedimento?.exige_face : false, exige_face: editingProcedimento?.tipo_regiao === 'DENTE' ? !!editingProcedimento?.exige_face : false,
num_faces: editingProcedimento?.num_faces || undefined,
num_raizes: editingProcedimento?.num_raizes || undefined,
valoresPlanos: planValues.map(pv => ({ valoresPlanos: planValues.map(pv => ({
planoId: pv.planoId!, planoId: pv.planoId!,
planoNome: planos?.find(p => p.id === pv.planoId)?.nome || '', planoNome: planos?.find(p => p.id === pv.planoId)?.nome || '',
@@ -523,6 +525,12 @@ export const ProcedimentosView: React.FC = () => {
} }
}; };
const normEsp = (s: string) => s.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
const procEspNome = normEsp(especialidades?.find(e => e.id === editingProcedimento?.especialidadeId)?.nome || '');
const isDentistica = procEspNome.includes('dentistic');
const isEndodontia = procEspNome.includes('endodont');
const hideArco = isDentistica || isEndodontia;
return ( return (
<AdminSection title="PROCEDIMENTOS E VALORES" buttonLabel="NOVO PROCEDIMENTO" onButtonClick={() => { setEditingProcedimento({}); setIsModalOpen(true); }}> <AdminSection title="PROCEDIMENTOS E VALORES" buttonLabel="NOVO PROCEDIMENTO" onButtonClick={() => { setEditingProcedimento({}); setIsModalOpen(true); }}>
<div className="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden"> <div className="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
@@ -654,7 +662,24 @@ export const ProcedimentosView: React.FC = () => {
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="text-xs font-bold text-gray-500 uppercase">ESPECIALIDADE</label> <label className="text-xs font-bold text-gray-500 uppercase">ESPECIALIDADE</label>
<select name="especialidadeId" defaultValue={editingProcedimento?.especialidadeId} className="w-full border border-gray-300 rounded-lg p-2 bg-white uppercase-select" required> <select
name="especialidadeId"
value={editingProcedimento?.especialidadeId || ''}
onChange={(e) => {
const espId = e.target.value;
const espNorm = normEsp(especialidades?.find(es => es.id === espId)?.nome || '');
const willHide = espNorm.includes('dentistic') || espNorm.includes('endodont');
setEditingProcedimento(prev => ({
...prev,
especialidadeId: espId,
tipo_regiao: (willHide && prev?.tipo_regiao === 'ARCO') ? 'DENTE' : prev?.tipo_regiao,
num_faces: undefined,
num_raizes: undefined,
}));
}}
className="w-full border border-gray-300 rounded-lg p-2 bg-white uppercase-select"
required
>
<option value="">SELECIONE UMA ESPECIALIDADE</option> <option value="">SELECIONE UMA ESPECIALIDADE</option>
{especialidades?.map(e => <option key={e.id} value={e.id}>{e.nome}</option>)} {especialidades?.map(e => <option key={e.id} value={e.id}>{e.nome}</option>)}
</select> </select>
@@ -672,11 +697,11 @@ export const ProcedimentosView: React.FC = () => {
<div className="space-y-3"> <div className="space-y-3">
<label className="text-xs font-bold text-gray-700 uppercase">EXIGÊNCIA DE REGIÃO NO LANÇAMENTO</label> <label className="text-xs font-bold text-gray-700 uppercase">EXIGÊNCIA DE REGIÃO NO LANÇAMENTO</label>
<div className="grid grid-cols-3 gap-2"> <div className={`grid gap-2 ${hideArco ? 'grid-cols-2' : 'grid-cols-3'}`}>
{[ {[
{ id: 'GERAL', label: 'PROCED. GERAL', desc: 'SEM ODONTOGRAMA' }, { id: 'GERAL', label: 'PROCED. GERAL', desc: 'SEM ODONTOGRAMA' },
{ id: 'DENTE', label: 'DENTE ESPECÍFICO', desc: 'LIBERA NÚMEROS' }, { id: 'DENTE', label: 'DENTE ESPECÍFICO', desc: 'LIBERA NÚMEROS' },
{ id: 'ARCO', label: 'ARCO COMPLETO', desc: 'LIBERA AI / AS' } ...(!hideArco ? [{ id: 'ARCO', label: 'ARCO COMPLETO', desc: 'LIBERA AI / AS' }] : [])
].map((opt) => ( ].map((opt) => (
<label <label
key={opt.id} key={opt.id}
@@ -721,6 +746,42 @@ export const ProcedimentosView: React.FC = () => {
</label> </label>
</div> </div>
)} )}
{isDentistica && (
<div className="pt-2 animate-in slide-in-from-top-2 duration-200">
<label className="text-[10px] font-black text-blue-700 uppercase tracking-widest mb-1.5 block">
de Faces (Dentística)
</label>
<select
value={editingProcedimento?.num_faces || ''}
onChange={(e) => setEditingProcedimento(prev => ({ ...prev, num_faces: e.target.value ? parseInt(e.target.value) : undefined }))}
className="w-full border border-blue-200 rounded-lg p-2.5 bg-white text-sm font-bold text-gray-700 focus:border-blue-500 outline-none"
>
<option value="">NÃO DEFINIDO</option>
{[1, 2, 3, 4, 5].map(n => (
<option key={n} value={n}>{n} {n === 1 ? 'face' : 'faces'}</option>
))}
</select>
</div>
)}
{isEndodontia && (
<div className="pt-2 animate-in slide-in-from-top-2 duration-200">
<label className="text-[10px] font-black text-blue-700 uppercase tracking-widest mb-1.5 block">
de Raízes (Endodontia)
</label>
<select
value={editingProcedimento?.num_raizes || ''}
onChange={(e) => setEditingProcedimento(prev => ({ ...prev, num_raizes: e.target.value ? parseInt(e.target.value) : undefined }))}
className="w-full border border-blue-200 rounded-lg p-2.5 bg-white text-sm font-bold text-gray-700 focus:border-blue-500 outline-none"
>
<option value="">NÃO DEFINIDO</option>
{[1, 2, 3, 4, 5].map(n => (
<option key={n} value={n}>{n} {n === 1 ? 'raiz' : 'raízes'}</option>
))}
</select>
</div>
)}
</div> </div>
<div className="pt-4"> <div className="pt-4">