From 4ff45e2c16c5622e418484bc5f1e53bf885a1952 Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Fri, 15 May 2026 04:00:55 +0200 Subject: [PATCH] =?UTF-8?q?feat(odontograma):=20denti=C3=A7=C3=A3o=20mista?= =?UTF-8?q?=20+=20fix=20mobile=20+=20fix=20dentes=20dec=C3=ADduos=20Q4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Botão expansor por modo: adulto mostra "+ INCLUIR DENTES DECÍDUOS", criança mostra "+ INCLUIR DENTES PERMANENTES" (Opção B) - Seção expandida exibe odontograma complementar com separadores âmbar e cor diferenciada: azul=permanente, âmbar=decíduo - Fechar expansão limpa automaticamente os dentes do tipo removido - Fix clínico: DENTES_CRIANCA.q4 corrigido de 41-46 (permanentes) para 81-85 (decíduos inferiores direitos) - Mobile: p-3 sm:p-8, gap-x-2 sm:gap-x-12, gap-1 sm:gap-2, w-7 h-7 sm:w-9 sm:h-9, text-[9px] sm:text-[11px] Co-Authored-By: Claude Sonnet 4.6 --- frontend/views/LancarGTO.tsx | 119 ++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 24 deletions(-) diff --git a/frontend/views/LancarGTO.tsx b/frontend/views/LancarGTO.tsx index 8c26007..3285961 100644 --- a/frontend/views/LancarGTO.tsx +++ b/frontend/views/LancarGTO.tsx @@ -80,10 +80,9 @@ const DENTES_ADULTO = { }; 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], + q4: [null, 83, 82, 81, null, null, 85, 84], // fix: eram 43-46 (permanentes) q3: [71, 72, 73, null, 74, 75, null, null] }; @@ -103,6 +102,7 @@ export const LancarGTO: React.FC = () => { const [selectedEsp, setSelectedEsp] = useState(''); const [selectedProc, setSelectedProc] = useState(null); const [pacienteTipo, setPacienteTipo] = useState<'ADULTO' | 'CRIANCA'>('ADULTO'); + const [showMixed, setShowMixed] = useState(false); const [selectedDentes, setSelectedDentes] = useState([]); const [selectedArco, setSelectedArco] = useState<'AI' | 'AS' | null>(null); const [face, setFace] = useState(''); @@ -244,9 +244,22 @@ export const LancarGTO: React.FC = () => { const totalGeral = store.items.reduce((sum, i) => sum + i.valorTotal, 0); + const toggleMixed = () => { + if (showMixed) { + // Ao fechar: remove os dentes do tipo expandido da seleção + if (pacienteTipo === 'ADULTO') { + setSelectedDentes(prev => prev.filter(d => parseInt(d) < 50)); + } else { + setSelectedDentes(prev => prev.filter(d => parseInt(d) >= 50)); + } + } + setShowMixed(prev => !prev); + }; + const renderDenteBtn = (num: number | string | null, keyIdx?: number) => { - if (num === null) return
; + if (num === null) return
; const sStr = num.toString(); + const isDeciduo = parseInt(sStr) >= 50; const isSelected = selectedDentes.includes(sStr); const isDisabled = !!selectedProc && selectedProc.tipo_regiao !== 'DENTE'; @@ -255,12 +268,16 @@ export const LancarGTO: React.FC = () => { 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 + className={`w-7 h-7 sm:w-9 sm:h-9 rounded-lg border-2 text-[9px] sm: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' + ? isDeciduo + ? 'bg-amber-500 border-amber-600 text-white scale-110 z-10' + : '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'} + : isDeciduo + ? 'bg-white border-amber-200 text-amber-700 hover:border-amber-400 hover:bg-amber-50' + : 'bg-white border-gray-200 text-gray-700 hover:border-blue-400 hover:bg-blue-50'} `} > {sStr} @@ -281,18 +298,18 @@ export const LancarGTO: React.FC = () => {
{/* ODONTOGRAM BOX */} -
-
+
+
@@ -300,35 +317,34 @@ export const LancarGTO: React.FC = () => {
{/* Dynamic Odontogram Grid */} -
- {/* Visual Separators */} +
-
+
-
+
{/* Q1 & Q2 (SUPERIOR) */} -
+
{pacienteTipo === 'ADULTO' && renderDenteBtn(DENTES_ADULTO.extras.q1)} -
+
{(pacienteTipo === 'ADULTO' ? DENTES_ADULTO.q1 : DENTES_CRIANCA.q1).map((d, i) => renderDenteBtn(d, i))}
-
-
+
+
{(pacienteTipo === 'ADULTO' ? DENTES_ADULTO.q2 : DENTES_CRIANCA.q2).map((d, i) => renderDenteBtn(d, i))}
{pacienteTipo === 'ADULTO' && renderDenteBtn(DENTES_ADULTO.extras.q2)}
{/* Q4 & Q3 (INFERIOR) */} -
+
{pacienteTipo === 'ADULTO' && renderDenteBtn(DENTES_ADULTO.extras.q4)} -
+
{(pacienteTipo === 'ADULTO' ? DENTES_ADULTO.q4 : DENTES_CRIANCA.q4).map((d, i) => renderDenteBtn(d, i))}
-
-
+
+
{(pacienteTipo === 'ADULTO' ? DENTES_ADULTO.q3 : DENTES_CRIANCA.q3).map((d, i) => renderDenteBtn(d, i))}
{pacienteTipo === 'ADULTO' && renderDenteBtn(DENTES_ADULTO.extras.q3)} @@ -336,6 +352,61 @@ export const LancarGTO: React.FC = () => {
+ {/* SEÇÃO MISTO — expande abaixo do grid principal */} + {showMixed && ( +
+

+ {pacienteTipo === 'ADULTO' ? '— DENTES DECÍDUOS —' : '— DENTES PERMANENTES —'} +

+
+
+
+
+ {/* Q1 & Q2 MISTO */} +
+ {pacienteTipo === 'CRIANCA' && renderDenteBtn(DENTES_ADULTO.extras.q1)} +
+ {(pacienteTipo === 'ADULTO' ? DENTES_CRIANCA.q1 : DENTES_ADULTO.q1).map((d, i) => renderDenteBtn(d, i))} +
+
+
+
+ {(pacienteTipo === 'ADULTO' ? DENTES_CRIANCA.q2 : DENTES_ADULTO.q2).map((d, i) => renderDenteBtn(d, i))} +
+ {pacienteTipo === 'CRIANCA' && renderDenteBtn(DENTES_ADULTO.extras.q2)} +
+ {/* Q4 & Q3 MISTO */} +
+ {pacienteTipo === 'CRIANCA' && renderDenteBtn(DENTES_ADULTO.extras.q4)} +
+ {(pacienteTipo === 'ADULTO' ? DENTES_CRIANCA.q4 : DENTES_ADULTO.q4).map((d, i) => renderDenteBtn(d, i))} +
+
+
+
+ {(pacienteTipo === 'ADULTO' ? DENTES_CRIANCA.q3 : DENTES_ADULTO.q3).map((d, i) => renderDenteBtn(d, i))} +
+ {pacienteTipo === 'CRIANCA' && renderDenteBtn(DENTES_ADULTO.extras.q3)} +
+
+
+
+ )} + + {/* BOTÃO EXPANSOR — dentição mista */} + +