feat(lancar-gto): dente click = add imediato ao rascunho com checkboxes de face
- Clicar dente adiciona GTOItem diretamente ao rascunho (sem botão "adicionar") - Clicar dente já adicionado remove o item do rascunho - Cada item no rascunho tem checkboxes O,M,D,V,L,P com comportamento radio (seleciona 1, desmarca outros) - Observação editável inline abaixo de cada item - Dentista avaliador (selecionado) aparece em cada linha do rascunho - ADICIONAR ITEM mantido apenas para procedimentos ARCO/GERAL - Validação de face obrigatória movida para FINALIZAR GTO Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+210
-231
@@ -3,7 +3,6 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
Trash2,
|
Trash2,
|
||||||
Check,
|
Check,
|
||||||
Building2,
|
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
PlusCircle,
|
PlusCircle,
|
||||||
Camera,
|
Camera,
|
||||||
@@ -46,6 +45,7 @@ interface GTOStore {
|
|||||||
setCredenciada: (c: string) => void;
|
setCredenciada: (c: string) => void;
|
||||||
addItem: (item: GTOItem) => void;
|
addItem: (item: GTOItem) => void;
|
||||||
removeItem: (id: string) => void;
|
removeItem: (id: string) => void;
|
||||||
|
updateItem: (id: string, update: Partial<Pick<GTOItem, 'face' | 'observacao'>>) => void;
|
||||||
clear: () => void;
|
clear: () => void;
|
||||||
setPendingSearch: (s: string) => void;
|
setPendingSearch: (s: string) => void;
|
||||||
}
|
}
|
||||||
@@ -65,13 +65,16 @@ const useGTOStore = create<GTOStore>((set) => ({
|
|||||||
removeItem: (id) => set((state) => ({
|
removeItem: (id) => set((state) => ({
|
||||||
items: state.items.filter(i => i.id !== id)
|
items: state.items.filter(i => i.id !== id)
|
||||||
})),
|
})),
|
||||||
|
updateItem: (id, update) => set((state) => ({
|
||||||
|
items: state.items.map(i => i.id === id ? { ...i, ...update } : i)
|
||||||
|
})),
|
||||||
clear: () => set({ paciente: null, dentista: null, items: [] }),
|
clear: () => set({ paciente: null, dentista: null, items: [] }),
|
||||||
setPendingSearch: (pendingSearch) => set({ pendingSearch }),
|
setPendingSearch: (pendingSearch) => set({ pendingSearch }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export { useGTOStore };
|
export { useGTOStore };
|
||||||
|
|
||||||
// --- ODONTOGRAM DATA (4x2 Grid Layout) ---
|
// --- ODONTOGRAM DATA ---
|
||||||
const DENTES_ADULTO = {
|
const DENTES_ADULTO = {
|
||||||
q1: [14, 13, 12, 11, 18, 17, 16, 15],
|
q1: [14, 13, 12, 11, 18, 17, 16, 15],
|
||||||
q2: [21, 22, 23, 24, 25, 26, 27, 28],
|
q2: [21, 22, 23, 24, 25, 26, 27, 28],
|
||||||
@@ -87,6 +90,8 @@ const DENTES_CRIANCA = {
|
|||||||
q3: [71, 72, 73, null, 74, 75, null, null]
|
q3: [71, 72, 73, null, 74, 75, null, null]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FACES = ['O', 'M', 'D', 'V', 'L', 'P'];
|
||||||
|
|
||||||
const normalize = (s: string) =>
|
const normalize = (s: string) =>
|
||||||
s.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().trim();
|
s.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().trim();
|
||||||
|
|
||||||
@@ -106,10 +111,6 @@ export const LancarGTO: React.FC = () => {
|
|||||||
const [showMixed, setShowMixed] = useState(false);
|
const [showMixed, setShowMixed] = useState(false);
|
||||||
const [selectedDentes, setSelectedDentes] = useState<string[]>([]);
|
const [selectedDentes, setSelectedDentes] = useState<string[]>([]);
|
||||||
const [selectedArco, setSelectedArco] = useState<'AI' | 'AS' | null>(null);
|
const [selectedArco, setSelectedArco] = useState<'AI' | 'AS' | null>(null);
|
||||||
|
|
||||||
// Per-tooth face/obs
|
|
||||||
const [denteInputs, setDenteInputs] = useState<Record<string, { face: string; obs: string }>>({});
|
|
||||||
// Arco/Geral face/obs (single)
|
|
||||||
const [arcoFace, setArcoFace] = useState('');
|
const [arcoFace, setArcoFace] = useState('');
|
||||||
const [arcoObs, setArcoObs] = useState('');
|
const [arcoObs, setArcoObs] = useState('');
|
||||||
|
|
||||||
@@ -164,26 +165,43 @@ export const LancarGTO: React.FC = () => {
|
|||||||
store.setPendingSearch('');
|
store.setPendingSearch('');
|
||||||
}, [pacientes, store.pendingSearch]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [pacientes, store.pendingSearch]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const resetProcSelection = () => {
|
// Tooth click → immediate add/remove from store
|
||||||
setSelectedDentes([]);
|
|
||||||
setSelectedArco(null);
|
|
||||||
setDenteInputs({});
|
|
||||||
setArcoFace('');
|
|
||||||
setArcoObs('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleDente = (num: string) => {
|
const toggleDente = (num: string) => {
|
||||||
if (selectedProc?.tipo_regiao !== 'DENTE') {
|
if (!selectedProc) {
|
||||||
|
toast.error("SELECIONE UM PROCEDIMENTO ANTES DE ESCOLHER O DENTE.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedProc.tipo_regiao !== 'DENTE') {
|
||||||
toast.error("ESTE PROCEDIMENTO NÃO PERMITE SELEÇÃO DE DENTE INDIVIDUAL.");
|
toast.error("ESTE PROCEDIMENTO NÃO PERMITE SELEÇÃO DE DENTE INDIVIDUAL.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSelectedArco(null);
|
|
||||||
if (selectedDentes.includes(num)) {
|
const existingItem = store.items.find(
|
||||||
setDenteInputs(di => { const next = { ...di }; delete next[num]; return next; });
|
i => i.dente === num && i.procedimentoId === selectedProc.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingItem) {
|
||||||
|
store.removeItem(existingItem.id);
|
||||||
setSelectedDentes(prev => prev.filter(d => d !== num));
|
setSelectedDentes(prev => prev.filter(d => d !== num));
|
||||||
} else {
|
} else {
|
||||||
setDenteInputs(di => ({ ...di, [num]: { face: '', obs: '' } }));
|
if (store.items.length >= 20) {
|
||||||
|
toast.error("LIMITE DE 20 ITENS ATINGIDO.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
store.addItem({
|
||||||
|
id: Math.random().toString(36).substring(7),
|
||||||
|
procedimentoId: selectedProc.id,
|
||||||
|
codigoTUSS: selectedProc.codigo || '000000',
|
||||||
|
codigoInterno: selectedProc.codigoInterno || '',
|
||||||
|
descricao: selectedProc.descricao_mobile || selectedProc.nome,
|
||||||
|
dente: num,
|
||||||
|
face: '',
|
||||||
|
quantidade: 1,
|
||||||
|
valorUnitario: selectedProc.valorParticular || 0,
|
||||||
|
valorTotal: selectedProc.valorParticular || 0,
|
||||||
|
});
|
||||||
setSelectedDentes(prev => [...prev, num]);
|
setSelectedDentes(prev => [...prev, num]);
|
||||||
|
setSelectedArco(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -193,52 +211,19 @@ export const LancarGTO: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSelectedDentes([]);
|
setSelectedDentes([]);
|
||||||
setDenteInputs({});
|
|
||||||
setSelectedArco(arco);
|
setSelectedArco(arco);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only used for ARCO / GERAL procedures
|
||||||
const handleAddItem = () => {
|
const handleAddItem = () => {
|
||||||
if (!selectedProc) {
|
if (!selectedProc) {
|
||||||
toast.error("SELECIONE UM PROCEDIMENTO PRIMEIRO.");
|
toast.error("SELECIONE UM PROCEDIMENTO PRIMEIRO.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (selectedProc.tipo_regiao === 'ARCO' && !selectedArco) {
|
||||||
toast.error("ESTE PROCEDIMENTO EXIGE A SELEÇÃO DE UM ARCO (AI/AS).");
|
toast.error("SELECIONE UM ARCO (AI/AS).");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedProc.tipo_regiao === 'DENTE') {
|
|
||||||
if (selectedProc.exige_face) {
|
|
||||||
const semFace = selectedDentes.filter(d => !denteInputs[d]?.face?.trim());
|
|
||||||
if (semFace.length > 0) {
|
|
||||||
toast.error(`FACE OBRIGATÓRIA PARA: ${semFace.join(', ')}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedDentes.forEach(dente => {
|
|
||||||
const inp = denteInputs[dente] || { face: '', obs: '' };
|
|
||||||
store.addItem({
|
|
||||||
id: Math.random().toString(36).substring(7),
|
|
||||||
procedimentoId: selectedProc.id,
|
|
||||||
codigoTUSS: selectedProc.codigo || '000000',
|
|
||||||
codigoInterno: selectedProc.codigoInterno || '',
|
|
||||||
descricao: selectedProc.nome,
|
|
||||||
dente,
|
|
||||||
face: inp.face,
|
|
||||||
quantidade: 1,
|
|
||||||
valorUnitario: selectedProc.valorParticular || 0,
|
|
||||||
valorTotal: selectedProc.valorParticular || 0,
|
|
||||||
observacao: inp.obs || undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
toast.success(`${selectedDentes.length} ITEM(S) ADICIONADO(S) AO RASCUNHO.`);
|
|
||||||
} else {
|
|
||||||
if (selectedProc.exige_face && !arcoFace.trim()) {
|
if (selectedProc.exige_face && !arcoFace.trim()) {
|
||||||
toast.error("A FACE É OBRIGATÓRIA PARA ESTE PROCEDIMENTO.");
|
toast.error("A FACE É OBRIGATÓRIA PARA ESTE PROCEDIMENTO.");
|
||||||
return;
|
return;
|
||||||
@@ -257,9 +242,9 @@ export const LancarGTO: React.FC = () => {
|
|||||||
observacao: arcoObs || undefined,
|
observacao: arcoObs || undefined,
|
||||||
});
|
});
|
||||||
toast.success("ADICIONADO AO RASCUNHO.");
|
toast.success("ADICIONADO AO RASCUNHO.");
|
||||||
}
|
setSelectedArco(null);
|
||||||
|
setArcoFace('');
|
||||||
resetProcSelection();
|
setArcoObs('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFinalize = async () => {
|
const handleFinalize = async () => {
|
||||||
@@ -267,12 +252,24 @@ export const LancarGTO: React.FC = () => {
|
|||||||
toast.error("PACIENTE, DENTISTA E PELO MENOS 1 ITEM SÃO OBRIGATÓRIOS.");
|
toast.error("PACIENTE, DENTISTA E PELO MENOS 1 ITEM SÃO OBRIGATÓRIOS.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const semFace = store.items.filter(i => {
|
||||||
|
if (!i.dente) return false;
|
||||||
|
const proc = procedimentos.find(p => p.id === i.procedimentoId);
|
||||||
|
return proc?.exige_face && !i.face;
|
||||||
|
});
|
||||||
|
if (semFace.length > 0) {
|
||||||
|
toast.error(`FACE OBRIGATÓRIA PARA ${semFace.map(i => `DENTE ${i.dente}`).join(', ')}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
toast.success("GTO GERADA COM SUCESSO!");
|
toast.success("GTO GERADA COM SUCESSO!");
|
||||||
store.clear();
|
store.clear();
|
||||||
setSelectedEsp('');
|
setSelectedEsp('');
|
||||||
setSelectedProc(null);
|
setSelectedProc(null);
|
||||||
resetProcSelection();
|
setSelectedDentes([]);
|
||||||
|
setSelectedArco(null);
|
||||||
|
setArcoFace('');
|
||||||
|
setArcoObs('');
|
||||||
} catch {
|
} catch {
|
||||||
toast.error("ERRO AO GERAR GTO.");
|
toast.error("ERRO AO GERAR GTO.");
|
||||||
}
|
}
|
||||||
@@ -284,11 +281,17 @@ export const LancarGTO: React.FC = () => {
|
|||||||
if (showMixed) {
|
if (showMixed) {
|
||||||
if (pacienteTipo === 'ADULTO') {
|
if (pacienteTipo === 'ADULTO') {
|
||||||
const toRemove = selectedDentes.filter(d => parseInt(d) >= 50);
|
const toRemove = selectedDentes.filter(d => parseInt(d) >= 50);
|
||||||
setDenteInputs(di => { const next = { ...di }; toRemove.forEach(d => delete next[d]); return next; });
|
toRemove.forEach(d => {
|
||||||
|
const item = store.items.find(i => i.dente === d && i.procedimentoId === selectedProc?.id);
|
||||||
|
if (item) store.removeItem(item.id);
|
||||||
|
});
|
||||||
setSelectedDentes(prev => prev.filter(d => parseInt(d) < 50));
|
setSelectedDentes(prev => prev.filter(d => parseInt(d) < 50));
|
||||||
} else {
|
} else {
|
||||||
const toRemove = selectedDentes.filter(d => parseInt(d) < 50);
|
const toRemove = selectedDentes.filter(d => parseInt(d) < 50);
|
||||||
setDenteInputs(di => { const next = { ...di }; toRemove.forEach(d => delete next[d]); return next; });
|
toRemove.forEach(d => {
|
||||||
|
const item = store.items.find(i => i.dente === d && i.procedimentoId === selectedProc?.id);
|
||||||
|
if (item) store.removeItem(item.id);
|
||||||
|
});
|
||||||
setSelectedDentes(prev => prev.filter(d => parseInt(d) >= 50));
|
setSelectedDentes(prev => prev.filter(d => parseInt(d) >= 50));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,18 +342,14 @@ export const LancarGTO: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<span className="font-black text-blue-800 text-sm uppercase truncate">{store.paciente.nome}</span>
|
<span className="font-black text-blue-800 text-sm uppercase truncate">{store.paciente.nome}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button onClick={() => store.setPaciente(null)}
|
||||||
onClick={() => store.setPaciente(null)}
|
className="text-xs font-bold text-blue-500 hover:text-red-500 transition-colors flex-shrink-0 ml-3 uppercase">
|
||||||
className="text-xs font-bold text-blue-500 hover:text-red-500 transition-colors flex-shrink-0 ml-3 uppercase"
|
|
||||||
>
|
|
||||||
ALTERAR
|
ALTERAR
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button onClick={() => setPatientSearchOpen(true)}
|
||||||
onClick={() => setPatientSearchOpen(true)}
|
className="w-full p-4 bg-gray-50 border-2 border-dashed border-blue-200 rounded-2xl font-black text-blue-400 text-sm hover:bg-blue-50 hover:border-blue-400 transition-all uppercase flex items-center justify-center gap-2">
|
||||||
className="w-full p-4 bg-gray-50 border-2 border-dashed border-blue-200 rounded-2xl font-black text-blue-400 text-sm hover:bg-blue-50 hover:border-blue-400 transition-all uppercase flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<Search size={16} /> SELECIONAR BENEFICIÁRIO
|
<Search size={16} /> SELECIONAR BENEFICIÁRIO
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -368,7 +367,8 @@ export const LancarGTO: React.FC = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSelectedEsp(e.target.value);
|
setSelectedEsp(e.target.value);
|
||||||
setSelectedProc(null);
|
setSelectedProc(null);
|
||||||
resetProcSelection();
|
setSelectedDentes([]);
|
||||||
|
setSelectedArco(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"
|
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"
|
||||||
>
|
>
|
||||||
@@ -383,7 +383,10 @@ export const LancarGTO: React.FC = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const pr = procedimentos.find(p => p.id === e.target.value);
|
const pr = procedimentos.find(p => p.id === e.target.value);
|
||||||
setSelectedProc(pr || null);
|
setSelectedProc(pr || null);
|
||||||
resetProcSelection();
|
setSelectedDentes([]);
|
||||||
|
setSelectedArco(null);
|
||||||
|
setArcoFace('');
|
||||||
|
setArcoObs('');
|
||||||
}}
|
}}
|
||||||
className="w-full p-4 bg-white border-2 border-orange-100 rounded-2xl font-black text-gray-800 text-sm shadow-inner uppercase"
|
className="w-full p-4 bg-white border-2 border-orange-100 rounded-2xl font-black text-gray-800 text-sm shadow-inner uppercase"
|
||||||
>
|
>
|
||||||
@@ -392,19 +395,20 @@ export const LancarGTO: React.FC = () => {
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
{selectedProc && (
|
{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">
|
<div className="bg-orange-50 p-3 rounded-xl border border-orange-100">
|
||||||
<p className="text-[9px] font-black text-orange-600 uppercase mb-1">REGRAS DE SELEÇÃO</p>
|
<div className="flex items-center gap-1.5 text-[10px] font-bold text-orange-800">
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<span className="text-[10px] font-bold text-orange-800 flex items-center gap-1">
|
|
||||||
<ChevronRight size={10} />
|
<ChevronRight size={10} />
|
||||||
{selectedProc.tipo_regiao === 'DENTE' ? 'EXIGE DENTE(S)' : selectedProc.tipo_regiao === 'ARCO' ? 'EXIGE ARCO (AI/AS)' : 'PROCEDIMENTO GERAL'}
|
{selectedProc.tipo_regiao === 'DENTE'
|
||||||
</span>
|
? 'CLIQUE OS DENTES NO ODONTOGRAMA'
|
||||||
{selectedProc.exige_face && (
|
: selectedProc.tipo_regiao === 'ARCO'
|
||||||
<span className="text-[10px] font-bold text-red-600 flex items-center gap-1">
|
? 'SELECIONE O ARCO (AI/AS)'
|
||||||
<ChevronRight size={10} /> EXIGE FACE CLÍNICA
|
: 'PROCEDIMENTO GERAL — CLIQUE ADICIONAR'}
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
{selectedProc.exige_face && (
|
||||||
|
<div className="flex items-center gap-1.5 text-[10px] font-bold text-red-600 mt-1">
|
||||||
|
<ChevronRight size={10} /> EXIGE FACE CLÍNICA
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -432,147 +436,72 @@ export const LancarGTO: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Per-tooth inputs (DENTE procedure) or unified (ARCO/GERAL)
|
// Step 4: only for ARCO / GERAL (face + obs + ADICIONAR button)
|
||||||
const isPerTooth = selectedProc?.tipo_regiao === 'DENTE' && selectedDentes.length > 0;
|
const step4Card = selectedProc && selectedProc.tipo_regiao !== 'DENTE' ? (
|
||||||
const step4Card = (
|
|
||||||
<div className="bg-white p-6 rounded-3xl border border-gray-100 shadow-xl space-y-4">
|
<div className="bg-white p-6 rounded-3xl border border-gray-100 shadow-xl space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest">
|
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest">04. Face & Observações</p>
|
||||||
04. {isPerTooth ? `Face / Obs por Dente (${selectedDentes.length})` : 'Face & Observações'}
|
{selectedProc.exige_face && <span className="text-red-500 text-[9px] font-black animate-pulse">* OBRIGATÓRIO</span>}
|
||||||
</p>
|
|
||||||
{selectedProc?.exige_face && (
|
|
||||||
<span className="text-red-500 text-[9px] font-black animate-pulse">* OBRIGATÓRIO</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isPerTooth ? (
|
|
||||||
<div className="space-y-3 max-h-64 overflow-y-auto pr-1">
|
|
||||||
{selectedDentes.map(dente => {
|
|
||||||
const isDeciduo = parseInt(dente) >= 50;
|
|
||||||
const inp = denteInputs[dente] || { face: '', obs: '' };
|
|
||||||
return (
|
|
||||||
<div key={dente} className="bg-gray-50 rounded-2xl p-3 border border-gray-100 space-y-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className={`w-8 h-8 rounded-lg flex items-center justify-center text-[11px] font-black flex-shrink-0
|
|
||||||
${isDeciduo ? 'bg-amber-100 text-amber-700 border border-amber-200' : 'bg-blue-100 text-blue-700 border border-blue-200'}`}>
|
|
||||||
{dente}
|
|
||||||
</span>
|
|
||||||
<span className="text-[10px] font-bold text-gray-500 uppercase truncate flex-1">
|
|
||||||
{selectedProc?.descricao_mobile || selectedProc?.nome}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={inp.face}
|
|
||||||
onChange={e => setDenteInputs(prev => ({
|
|
||||||
...prev,
|
|
||||||
[dente]: { ...(prev[dente] || {}), face: e.target.value.toUpperCase() }
|
|
||||||
}))}
|
|
||||||
placeholder={selectedProc?.exige_face ? 'FACE *' : 'O,M,D,V,L,P'}
|
|
||||||
className={`p-2 border-2 rounded-xl text-[11px] font-black outline-none transition-all
|
|
||||||
${selectedProc?.exige_face && !inp.face
|
|
||||||
? 'border-red-200 bg-red-50 focus:border-red-400'
|
|
||||||
: 'border-gray-200 bg-white focus:border-amber-400'}`}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={inp.obs}
|
|
||||||
onChange={e => setDenteInputs(prev => ({
|
|
||||||
...prev,
|
|
||||||
[dente]: { ...(prev[dente] || {}), obs: e.target.value }
|
|
||||||
}))}
|
|
||||||
placeholder="OBS"
|
|
||||||
className="p-2 border-2 border-gray-200 bg-white rounded-xl text-[11px] outline-none focus:border-amber-400 transition-all"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="grid grid-cols-1 gap-4">
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2">
|
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2">Face (O, M, D, V, L, P)</label>
|
||||||
Face (O, M, D, V, L, P)
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={arcoFace}
|
value={arcoFace}
|
||||||
onChange={e => setArcoFace(e.target.value.toUpperCase())}
|
onChange={e => setArcoFace(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
|
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'}
|
${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 ? "FACE OBRIGATÓRIA..." : "OPCIONAL"}
|
||||||
placeholder={selectedProc?.exige_face ? "DIGITE AS FACES..." : "OPCIONAL"}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 italic">Observações Clínicas</label>
|
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 italic">Observações</label>
|
||||||
<div className="relative">
|
|
||||||
<textarea
|
<textarea
|
||||||
value={arcoObs}
|
value={arcoObs}
|
||||||
onChange={e => setArcoObs(e.target.value)}
|
onChange={e => setArcoObs(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"
|
rows={3}
|
||||||
|
className="w-full p-4 bg-gray-50 border-2 border-gray-100 rounded-2xl text-[11px] focus:border-amber-400 outline-none transition-all resize-none"
|
||||||
/>
|
/>
|
||||||
<PlusCircle size={24} className="absolute bottom-4 right-4 text-green-500 opacity-50" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const actionsDesktop = (
|
|
||||||
<div className="grid grid-cols-4 gap-4 p-2">
|
|
||||||
<button
|
<button
|
||||||
onClick={handleAddItem}
|
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"
|
className="w-full bg-[#2d6a4f] text-white py-4 rounded-2xl font-black text-sm uppercase shadow-lg shadow-green-100 hover:bg-[#1b4332] active:scale-95 transition-all flex items-center justify-center gap-2 group"
|
||||||
>
|
>
|
||||||
ADICIONAR ITEM <ChevronRight className="group-hover:translate-x-1 transition-transform" />
|
ADICIONAR ITEM <ChevronRight size={16} className="group-hover:translate-x-1 transition-transform" />
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
// Finalize button (shared)
|
||||||
|
const finalizeBtn = (
|
||||||
|
<div className="p-2">
|
||||||
<button
|
<button
|
||||||
onClick={handleFinalize}
|
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"
|
className="w-full bg-gray-900 text-white py-5 rounded-3xl font-black text-lg uppercase shadow-xl hover:bg-black active:scale-95 transition-all flex items-center justify-center gap-3"
|
||||||
title="FINALIZAR GTO"
|
|
||||||
>
|
>
|
||||||
<Check size={32} strokeWidth={4} />
|
<Check size={22} strokeWidth={3} /> FINALIZAR GTO
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const actionsMobile = (
|
|
||||||
<button
|
|
||||||
onClick={handleAddItem}
|
|
||||||
className="w-full 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>
|
|
||||||
);
|
|
||||||
|
|
||||||
const odontogramCard = (
|
const odontogramCard = (
|
||||||
<div className="bg-white rounded-3xl border border-gray-100 shadow-xl p-3 sm:p-8 space-y-4 sm:space-y-6">
|
<div className="bg-white rounded-3xl border border-gray-100 shadow-xl p-3 sm:p-8 space-y-4 sm:space-y-6">
|
||||||
<div className="flex justify-center mb-2 sm:mb-4">
|
<div className="flex justify-center mb-2 sm:mb-4">
|
||||||
<div className="inline-flex bg-gray-100 p-1 rounded-xl shadow-inner">
|
<div className="inline-flex bg-gray-100 p-1 rounded-xl shadow-inner">
|
||||||
<button
|
<button
|
||||||
onClick={() => { setPacienteTipo('ADULTO'); setSelectedDentes([]); setDenteInputs({}); setShowMixed(false); }}
|
onClick={() => { setPacienteTipo('ADULTO'); setSelectedDentes([]); setShowMixed(false); }}
|
||||||
className={`px-5 sm: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'}`}
|
className={`px-5 sm: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>
|
||||||
Adulto
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => { setPacienteTipo('CRIANCA'); setSelectedDentes([]); setDenteInputs({}); setShowMixed(false); }}
|
onClick={() => { setPacienteTipo('CRIANCA'); setSelectedDentes([]); setShowMixed(false); }}
|
||||||
className={`px-5 sm: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'}`}
|
className={`px-5 sm: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>
|
||||||
Criança
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative border-t border-b border-gray-50 py-6 sm:py-10">
|
<div className="relative border-t border-b border-gray-50 py-6 sm:py-10">
|
||||||
<div className="absolute left-1/2 top-4 bottom-4 w-[2px] bg-gray-200 -translate-x-1/2"></div>
|
<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-2 right-2 h-[2px] bg-gray-200 -translate-y-1/2"></div>
|
<div className="absolute top-1/2 left-2 right-2 h-[2px] bg-gray-200 -translate-y-1/2"></div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-x-2 sm:gap-x-12 gap-y-6 sm:gap-y-16">
|
<div className="grid grid-cols-2 gap-x-2 sm:gap-x-12 gap-y-6 sm:gap-y-16">
|
||||||
<div className="flex justify-end items-center gap-1 sm:gap-2 pr-1 sm:pr-2">
|
<div className="flex justify-end items-center gap-1 sm:gap-2 pr-1 sm:pr-2">
|
||||||
{pacienteTipo === 'ADULTO' && renderDenteBtn(DENTES_ADULTO.extras.q1)}
|
{pacienteTipo === 'ADULTO' && renderDenteBtn(DENTES_ADULTO.extras.q1)}
|
||||||
@@ -642,9 +571,7 @@ export const LancarGTO: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={toggleMixed}
|
onClick={toggleMixed}
|
||||||
className={`w-full py-2.5 rounded-2xl border-2 font-black text-[11px] uppercase transition-all flex items-center justify-center gap-2
|
className={`w-full py-2.5 rounded-2xl border-2 font-black text-[11px] uppercase transition-all flex items-center justify-center gap-2
|
||||||
${showMixed
|
${showMixed ? 'border-amber-300 text-amber-600 bg-amber-50 hover:bg-amber-100' : 'border-dashed border-amber-200 text-amber-400 hover:border-amber-400 hover:bg-amber-50'}`}
|
||||||
? 'border-amber-300 text-amber-600 bg-amber-50 hover:bg-amber-100'
|
|
||||||
: 'border-dashed border-amber-200 text-amber-400 hover:border-amber-400 hover:bg-amber-50'}`}
|
|
||||||
>
|
>
|
||||||
{showMixed
|
{showMixed
|
||||||
? <><X size={13} /> {pacienteTipo === 'ADULTO' ? 'REMOVER DECÍDUOS' : 'REMOVER PERMANENTES'}</>
|
? <><X size={13} /> {pacienteTipo === 'ADULTO' ? 'REMOVER DECÍDUOS' : 'REMOVER PERMANENTES'}</>
|
||||||
@@ -653,83 +580,138 @@ export const LancarGTO: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex justify-center gap-4 pt-4">
|
<div className="flex justify-center gap-4 pt-4">
|
||||||
<button
|
{(['AI', 'AS'] as const).map(arco => (
|
||||||
onClick={() => selectArco('AI')}
|
<button key={arco}
|
||||||
|
onClick={() => selectArco(arco)}
|
||||||
disabled={!!selectedProc && selectedProc.tipo_regiao !== 'ARCO'}
|
disabled={!!selectedProc && selectedProc.tipo_regiao !== 'ARCO'}
|
||||||
className={`px-10 py-3 rounded-2xl border-2 font-black text-sm uppercase transition-all shadow-sm
|
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' :
|
${selectedArco === arco ? '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' :
|
(!!selectedProc && selectedProc.tipo_regiao !== 'ARCO')
|
||||||
'bg-white border-gray-100 text-gray-500 hover:border-amber-400'}`}
|
? '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
|
{arco === 'AI' ? 'AI - Inferior' : 'AS - Superior'}
|
||||||
</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>
|
</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Rascunho with inline face checkboxes + obs + dentist
|
||||||
const rascunhoCard = (
|
const rascunhoCard = (
|
||||||
<div className="bg-white rounded-3xl border border-gray-100 overflow-hidden shadow-xl">
|
<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">
|
<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>
|
<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 className="flex items-center gap-3">
|
||||||
|
<div className="bg-white/20 px-3 py-1 rounded-full text-[10px]">{store.items.length}/20</div>
|
||||||
|
<div className="bg-white/20 px-3 py-1 rounded-full text-[10px]">
|
||||||
|
R$ {totalGeral.toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6 space-y-4">
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 sm:p-6 space-y-3">
|
||||||
{store.items.length === 0 && (
|
{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="py-14 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">
|
<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} />
|
<AlertCircle size={32} />
|
||||||
</div>
|
</div>
|
||||||
NÃO HÁ ITENS ADICIONADOS
|
<span className="text-xs">SELECIONE DENTES OU ARCOS<br/>PARA INICIAR O RASCUNHO</span>
|
||||||
</div>
|
</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">
|
{store.items.map((item) => {
|
||||||
<div className="flex justify-between items-start mb-4">
|
const isDeciduo = item.dente ? parseInt(item.dente) >= 50 : false;
|
||||||
<div className="space-y-1">
|
return (
|
||||||
<div className="flex items-center gap-3">
|
<div key={item.id}
|
||||||
<span className="bg-blue-600 text-white text-[10px] font-black px-2 py-1 rounded-md uppercase shadow-sm">COD {item.codigoTUSS}</span>
|
className="border border-gray-100 rounded-2xl p-4 bg-gray-50/40 hover:bg-white transition-all shadow-sm">
|
||||||
<h4 className="font-black text-gray-900 text-sm uppercase tracking-tight">{item.descricao}</h4>
|
|
||||||
|
{/* Header row: tooth/arco chip + procedure + value + remove */}
|
||||||
|
<div className="flex items-start justify-between gap-2 mb-3">
|
||||||
|
<div className="flex items-center gap-2.5 min-w-0 flex-1">
|
||||||
|
<span className={`w-10 h-10 rounded-xl flex items-center justify-center text-[11px] font-black flex-shrink-0 border-2
|
||||||
|
${item.dente
|
||||||
|
? isDeciduo
|
||||||
|
? 'bg-amber-100 text-amber-700 border-amber-200'
|
||||||
|
: 'bg-blue-100 text-blue-700 border-blue-200'
|
||||||
|
: 'bg-amber-100 text-amber-600 border-amber-200'}`}>
|
||||||
|
{item.dente || item.arco || '--'}
|
||||||
|
</span>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="font-black text-xs text-gray-900 uppercase leading-tight truncate">
|
||||||
|
{item.descricao}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-[10px] text-gray-400 font-bold uppercase">
|
<div className="flex items-center gap-1 mt-0.5 text-[9px] text-gray-400 font-bold uppercase">
|
||||||
<Users size={12} /> {store.paciente?.nome}
|
<Stethoscope size={8} />
|
||||||
|
<span className="truncate">{store.dentista?.nome || 'SEM DENTISTA'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => store.removeItem(item.id)} className="text-gray-300 hover:text-red-600 p-2 transition-colors">
|
</div>
|
||||||
<Trash2 size={20} />
|
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||||
|
<span className="text-[10px] font-black text-green-600 whitespace-nowrap">
|
||||||
|
R$ {item.valorTotal.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
store.removeItem(item.id);
|
||||||
|
if (item.dente) setSelectedDentes(prev => prev.filter(d => d !== item.dente));
|
||||||
|
}}
|
||||||
|
className="text-gray-300 hover:text-red-500 transition-colors p-0.5"
|
||||||
|
>
|
||||||
|
<Trash2 size={15} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
{item.observacao && (
|
|
||||||
<div className="text-[11px] text-gray-500 bg-white p-3 border border-gray-100 rounded-xl leading-relaxed">
|
{/* Face checkboxes — only for tooth items */}
|
||||||
<span className="font-black text-gray-400 uppercase mr-2 opacity-50">OBS:</span> {item.observacao}
|
{item.dente && (
|
||||||
|
<div className="flex items-center gap-1.5 mb-2.5 flex-wrap">
|
||||||
|
<span className="text-[9px] font-black text-gray-400 uppercase tracking-wider w-full mb-1">
|
||||||
|
FACE CLÍNICA:
|
||||||
|
{!item.face && (
|
||||||
|
<span className="text-gray-300 font-normal normal-case ml-1 italic">não selecionada</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{FACES.map(f => (
|
||||||
|
<button
|
||||||
|
key={f}
|
||||||
|
onClick={() => store.updateItem(item.id, { face: item.face === f ? '' : f })}
|
||||||
|
className={`w-9 h-9 rounded-xl border-2 text-[11px] font-black transition-all flex items-center justify-center
|
||||||
|
${item.face === f
|
||||||
|
? 'bg-blue-600 border-blue-700 text-white shadow-md'
|
||||||
|
: 'bg-white border-gray-200 text-gray-400 hover:border-blue-300 hover:text-blue-500 hover:bg-blue-50'}`}
|
||||||
|
>
|
||||||
|
{f}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Observação */}
|
||||||
|
<textarea
|
||||||
|
value={item.observacao || ''}
|
||||||
|
onChange={e => store.updateItem(item.id, { observacao: e.target.value })}
|
||||||
|
placeholder="OBSERVAÇÃO CLÍNICA..."
|
||||||
|
rows={2}
|
||||||
|
className="w-full px-3 py-2.5 bg-white border border-gray-100 rounded-xl text-[11px] resize-none outline-none focus:border-amber-300 transition-colors placeholder:text-gray-300 leading-relaxed"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
<div className="pt-6 border-t-2 border-dotted border-gray-200 flex justify-between items-center">
|
})}
|
||||||
|
|
||||||
|
{store.items.length > 0 && (
|
||||||
|
<div className="pt-4 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">
|
<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
|
<Camera size={16} /> ANEXAR DOCUMENTOS
|
||||||
</button>
|
</button>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] mb-1">TOTAL ACUMULADO</p>
|
<p className="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] mb-1">TOTAL</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>
|
<h3 className="text-2xl font-black text-gray-900 px-4 py-2 bg-gray-50 rounded-2xl border border-gray-100 shadow-inner">
|
||||||
|
R$ {totalGeral.toFixed(2)}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -754,7 +736,7 @@ export const LancarGTO: React.FC = () => {
|
|||||||
{step2Card}
|
{step2Card}
|
||||||
{step3Card}
|
{step3Card}
|
||||||
{step4Card}
|
{step4Card}
|
||||||
{actionsDesktop}
|
{finalizeBtn}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -771,7 +753,6 @@ export const LancarGTO: React.FC = () => {
|
|||||||
{mobilePage === 3 && (
|
{mobilePage === 3 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{step4Card}
|
{step4Card}
|
||||||
{actionsMobile}
|
|
||||||
{rascunhoCard}
|
{rascunhoCard}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -792,9 +773,7 @@ export const LancarGTO: React.FC = () => {
|
|||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{([1, 2, 3] as const).map(p => (
|
{([1, 2, 3] as const).map(p => (
|
||||||
<button
|
<button key={p} onClick={() => setMobilePage(p)}
|
||||||
key={p}
|
|
||||||
onClick={() => setMobilePage(p)}
|
|
||||||
className={`transition-all rounded-full ${mobilePage === p ? 'w-6 h-2.5 bg-blue-600' : 'w-2.5 h-2.5 bg-gray-200 hover:bg-gray-400'}`}
|
className={`transition-all rounded-full ${mobilePage === p ? 'w-6 h-2.5 bg-blue-600' : 'w-2.5 h-2.5 bg-gray-200 hover:bg-gray-400'}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user