From f6fad55ff30507c6ff477ef28510ca68344d51b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Tue, 25 Nov 2025 12:39:50 -0300 Subject: [PATCH 01/37] fix-visual-adjustments --- .../app/(main-routes)/calendar/page.tsx | 2 +- susconecta/app/laudos-editor/page.tsx | 65 ++++++++++++-- susconecta/app/laudos/[id]/editar/page.tsx | 85 +++++++++++++------ .../paciente/resultados/ResultadosClient.tsx | 11 --- susconecta/app/profissional/page.tsx | 6 +- .../components/features/dashboard/header.tsx | 2 +- 6 files changed, 123 insertions(+), 48 deletions(-) diff --git a/susconecta/app/(main-routes)/calendar/page.tsx b/susconecta/app/(main-routes)/calendar/page.tsx index cf86cd4..da2c9dc 100644 --- a/susconecta/app/(main-routes)/calendar/page.tsx +++ b/susconecta/app/(main-routes)/calendar/page.tsx @@ -309,7 +309,7 @@ export default function AgendamentoPage() { {/* legenda dinâmica: mostra as cores presentes nos agendamentos do dia atual */} -
+
diff --git a/susconecta/app/laudos-editor/page.tsx b/susconecta/app/laudos-editor/page.tsx index 52bd823..4f69751 100644 --- a/susconecta/app/laudos-editor/page.tsx +++ b/susconecta/app/laudos-editor/page.tsx @@ -178,6 +178,33 @@ export default function LaudosEditorPage() { } }, []); + // Auto-salvar no localStorage sempre que houver mudanças (com debounce) + useEffect(() => { + const timeoutId = setTimeout(() => { + // Capturar conteúdo atual do editor antes de salvar + const currentContent = editorRef.current?.innerHTML || content; + + const draft = { + pacienteSelecionado, + content: currentContent, + campos, + solicitanteId, + solicitanteNome, + prazoDate, + prazoTime, + imagens, + lastSaved: new Date().toISOString(), + }; + + // Só salvar se houver conteúdo ou dados preenchidos + if (currentContent || pacienteSelecionado || campos.exame || campos.diagnostico || imagens.length > 0) { + localStorage.setItem('laudoDraft', JSON.stringify(draft)); + } + }, 1000); // Aguarda 1 segundo após última mudança + + return () => clearTimeout(timeoutId); + }, [pacienteSelecionado, content, campos, solicitanteId, solicitanteNome, prazoDate, prazoTime, imagens]); + // Tentar obter o registro de médico correspondente ao usuário autenticado useEffect(() => { let mounted = true; @@ -247,6 +274,23 @@ export default function LaudosEditorPage() { } }, [content]); + // Função para trocar de aba salvando conteúdo antes + const handleTabChange = (newTab: string) => { + // Salvar conteúdo do editor antes de trocar + if (editorRef.current) { + const editorContent = editorRef.current.innerHTML; + setContent(editorContent); + } + setActiveTab(newTab); + }; + + // Restaurar conteúdo do editor quando voltar para a aba editor + useEffect(() => { + if (activeTab === 'editor' && editorRef.current && content) { + editorRef.current.innerHTML = content; + } + }, [activeTab]); + // Desfazer const handleUndo = () => { if (historyIndex > 0) { @@ -321,11 +365,15 @@ export default function LaudosEditorPage() { // Salvar rascunho no localStorage const saveDraft = () => { + // Capturar conteúdo atual do editor antes de salvar + const currentContent = editorRef.current?.innerHTML || content; + const draft = { pacienteSelecionado, - content, + content: currentContent, campos, solicitanteId, + solicitanteNome, prazoDate, prazoTime, imagens, @@ -389,6 +437,9 @@ export default function LaudosEditorPage() { return; } + // Capturar conteúdo atual do editor antes de salvar + const currentContent = editorRef.current?.innerHTML || content; + const userId = user?.id || '00000000-0000-0000-0000-000000000001'; let composedDueAt = undefined; @@ -404,7 +455,7 @@ export default function LaudosEditorPage() { diagnosis: campos.diagnostico || '', conclusion: campos.conclusao || '', cid_code: campos.cid || '', - content_html: content, + content_html: currentContent, content_json: {}, requested_by: solicitanteId || userId, due_at: composedDueAt ?? new Date().toISOString(), @@ -414,6 +465,10 @@ export default function LaudosEditorPage() { if (createNewReport) { await createNewReport(payload as any); + + // Limpar rascunho salvo após sucesso + localStorage.removeItem('laudoDraft'); + toast({ title: 'Laudo criado com sucesso!', description: 'O laudo foi liberado e salvo.', @@ -536,7 +591,7 @@ export default function LaudosEditorPage() { {/* Tabs */}
- {/* Mais filtros / Voltar */} -
- -
- {/* Voltar */}
{dropdownOpen && ( -
+

From 8df4239406a23889772fa30a34da7197cec3b2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Thu, 27 Nov 2025 16:00:39 -0300 Subject: [PATCH 02/37] fix: patient-section --- susconecta/app/(main-routes)/pacientes/layout.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/susconecta/app/(main-routes)/pacientes/layout.tsx b/susconecta/app/(main-routes)/pacientes/layout.tsx index 4e9bd5c..daac08d 100644 --- a/susconecta/app/(main-routes)/pacientes/layout.tsx +++ b/susconecta/app/(main-routes)/pacientes/layout.tsx @@ -1,11 +1,5 @@ import type { ReactNode } from "react"; -import { ChatWidget } from "@/components/features/pacientes/chat-widget"; export default function PacientesLayout({ children }: { children: ReactNode }) { - return ( - <> - {children} - - - ); + return <>{children}; } From fda4e5651ad21ea11f28761068eac6b810723bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:10:18 -0300 Subject: [PATCH 03/37] fix: changes-in-patients-appoiments --- .../paciente/resultados/ResultadosClient.tsx | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/susconecta/app/paciente/resultados/ResultadosClient.tsx b/susconecta/app/paciente/resultados/ResultadosClient.tsx index c8dd2ff..10dd745 100644 --- a/susconecta/app/paciente/resultados/ResultadosClient.tsx +++ b/susconecta/app/paciente/resultados/ResultadosClient.tsx @@ -990,7 +990,7 @@ export default function ResultadosClient() { {/* Ações */}

-
) @@ -1175,8 +1165,17 @@ export default function ResultadosClient() {
- setMoreTimesDate(e.target.value)} /> - + { + setMoreTimesDate(e.target.value) + if (moreTimesForDoctor) { + void fetchSlotsForDate(moreTimesForDoctor, e.target.value) + } + }} + />
@@ -1185,12 +1184,14 @@ export default function ResultadosClient() { ) : moreTimesException ? (
{moreTimesException}
) : (moreTimesSlots.length ? ( -
- {moreTimesSlots.map(s => ( - - ))} +
+
+ {moreTimesSlots.map(s => ( + + ))} +
) : (
Sem horários para a data selecionada.
From 19260f7e2751cba28c061683ce7509b84f68e10b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:25:47 -0300 Subject: [PATCH 04/37] fix: hover-errors --- susconecta/components/features/general/event-manager.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/susconecta/components/features/general/event-manager.tsx b/susconecta/components/features/general/event-manager.tsx index 38bac08..285c448 100644 --- a/susconecta/components/features/general/event-manager.tsx +++ b/susconecta/components/features/general/event-manager.tsx @@ -336,11 +336,11 @@ export function EventManager({ {view === "list" && "Todos os eventos"}
- -
From 73eb35b21b34b1d8355a8a1f928b931302257391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:52:01 -0300 Subject: [PATCH 05/37] fix: calendar-colors --- susconecta/app/(main-routes)/calendar/page.tsx | 4 ++-- susconecta/components/features/general/event-manager.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/susconecta/app/(main-routes)/calendar/page.tsx b/susconecta/app/(main-routes)/calendar/page.tsx index da2c9dc..24ae39c 100644 --- a/susconecta/app/(main-routes)/calendar/page.tsx +++ b/susconecta/app/(main-routes)/calendar/page.tsx @@ -157,7 +157,7 @@ export default function AgendamentoPage() { // Mapa de classes para cores conhecidas const colorClassMap: Record = { blue: "bg-blue-500 ring-blue-500/20", - green: "bg-green-500 ring-green-500/20", + green: "bg-[#10B981] ring-[#10B981]/20", orange: "bg-orange-500 ring-orange-500/20", red: "bg-red-500 ring-red-500/20", purple: "bg-purple-500 ring-purple-500/20", @@ -242,7 +242,7 @@ export default function AgendamentoPage() { Solicitado
- + Confirmado
diff --git a/susconecta/components/features/general/event-manager.tsx b/susconecta/components/features/general/event-manager.tsx index 285c448..9dd775c 100644 --- a/susconecta/components/features/general/event-manager.tsx +++ b/susconecta/components/features/general/event-manager.tsx @@ -55,7 +55,7 @@ export interface EventManagerProps { const defaultColors = [ { name: "Blue", value: "blue", bg: "bg-blue-500", text: "text-blue-700" }, - { name: "Green", value: "green", bg: "bg-green-500", text: "text-green-700" }, + { name: "Green", value: "green", bg: "bg-[#10B981]", text: "text-green-700" }, { name: "Purple", value: "purple", bg: "bg-purple-500", text: "text-purple-700" }, { name: "Orange", value: "orange", bg: "bg-orange-500", text: "text-orange-700" }, { name: "Pink", value: "pink", bg: "bg-pink-500", text: "text-pink-700" }, From 33643df28b753eb88d061844ef37c36e45ac80e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:56:00 -0300 Subject: [PATCH 06/37] fix: appoiment-colors --- .../app/(main-routes)/consultas/page.tsx | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/susconecta/app/(main-routes)/consultas/page.tsx b/susconecta/app/(main-routes)/consultas/page.tsx index fe97473..ac76758 100644 --- a/susconecta/app/(main-routes)/consultas/page.tsx +++ b/susconecta/app/(main-routes)/consultas/page.tsx @@ -567,13 +567,19 @@ export default function ConsultasPage() { {translateStatus(appointment.status)} @@ -658,13 +664,21 @@ export default function ConsultasPage() {
Status
{translateStatus(appointment.status)} @@ -777,13 +791,19 @@ export default function ConsultasPage() { {translateStatus(viewingAppointment?.status || "")} From cfb22ebb7647199c202d9f6fe033339cf288de42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Thu, 27 Nov 2025 18:03:41 -0300 Subject: [PATCH 07/37] fix: calendar-registration --- .../forms/calendar-registration-form.tsx | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/susconecta/components/features/forms/calendar-registration-form.tsx b/susconecta/components/features/forms/calendar-registration-form.tsx index afc2aea..d6a415c 100644 --- a/susconecta/components/features/forms/calendar-registration-form.tsx +++ b/susconecta/components/features/forms/calendar-registration-form.tsx @@ -869,22 +869,39 @@ export function CalendarRegistrationForm({ formData, onFormChange, createMode =
{loadingPatient ? ( -
Carregando dados do paciente...
+
Carregando dados do paciente...
) : patientDetails ? ( patientDetails.error ? (
Erro ao carregar paciente: {String(patientDetails.error)}
) : ( -
-
CPF: {patientDetails.cpf || '-'}
-
Telefone: {patientDetails.phone_mobile || patientDetails.telefone || '-'}
-
E-mail: {patientDetails.email || '-'}
-
Data de nascimento: {patientDetails.birth_date || '-'}
+
+
+ CPF: + {patientDetails.cpf || '-'} +
+
+ Telefone: + {patientDetails.phone_mobile || patientDetails.telefone || '-'} +
+
+ E-mail: + {patientDetails.email || '-'} +
+
+ Data de nascimento: + + {patientDetails.birth_date + ? new Date(patientDetails.birth_date + 'T00:00:00').toLocaleDateString('pt-BR') + : '-' + } + +
) ) : (
Paciente não vinculado
)} -
Para editar os dados do paciente, acesse a ficha do paciente.
+
Para editar os dados do paciente, acesse a ficha do paciente.
From 386202bce0088964531ea2600736fff83ca0660d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Thu, 27 Nov 2025 18:10:48 -0300 Subject: [PATCH 08/37] add: logo-in-patient-and-doctor-page --- susconecta/app/paciente/page.tsx | 12 ++++++++++++ susconecta/app/profissional/page.tsx | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx index 22ff30e..2a742ab 100644 --- a/susconecta/app/paciente/page.tsx +++ b/susconecta/app/paciente/page.tsx @@ -1927,6 +1927,18 @@ export default function PacientePage() { {/* Header com informações do paciente */}
+ {/* Logo MEDIConnect */} +
+
+ +
+ + MEDIConnect + +
+ +
+ {profileData.nome?.charAt(0) || 'P'} diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx index 38f5114..00a9662 100644 --- a/susconecta/app/profissional/page.tsx +++ b/susconecta/app/profissional/page.tsx @@ -3042,6 +3042,18 @@ const ProfissionalPage = () => {
{/* Logo/Avatar Section */}
+ {/* Logo MEDIConnect */} +
+
+ +
+ + MEDIConnect + +
+ +
+ From 6c0a6f7367edb07b55c817b7cc3961a0d18f0b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Thu, 27 Nov 2025 20:34:23 -0300 Subject: [PATCH 09/37] fix: laudo-page --- susconecta/app/laudos-editor/page.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/susconecta/app/laudos-editor/page.tsx b/susconecta/app/laudos-editor/page.tsx index 4f69751..bd0eeb9 100644 --- a/susconecta/app/laudos-editor/page.tsx +++ b/susconecta/app/laudos-editor/page.tsx @@ -148,7 +148,10 @@ export default function LaudosEditorPage() { if (savedDraft) { try { const draft = JSON.parse(savedDraft); - setPacienteSelecionado(draft.pacienteSelecionado); + // Carregar paciente do rascunho se existir + if (draft.pacienteSelecionado) { + setPacienteSelecionado(draft.pacienteSelecionado); + } setContent(draft.content); setCampos(draft.campos); setSolicitanteId(draft.solicitanteId); From 621817e9639efc50f1aae5b1f24669fe0ae97f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Thu, 27 Nov 2025 21:07:27 -0300 Subject: [PATCH 10/37] fix: report-edit --- susconecta/app/laudos/[id]/editar/page.tsx | 24 +++++++++------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/susconecta/app/laudos/[id]/editar/page.tsx b/susconecta/app/laudos/[id]/editar/page.tsx index 30362ba..912d531 100644 --- a/susconecta/app/laudos/[id]/editar/page.tsx +++ b/susconecta/app/laudos/[id]/editar/page.tsx @@ -89,13 +89,6 @@ export default function EditarLaudoPage() { return () => clearTimeout(timeoutId); }, [content, campos, laudoId]); - // Sincronizar conteúdo com o editor - useEffect(() => { - if (editorRef.current && content) { - editorRef.current.innerHTML = content; - } - }, [content]); - // Função para trocar de aba salvando conteúdo antes const handleTabChange = (newTab: string) => { // Salvar conteúdo do editor antes de trocar @@ -103,16 +96,19 @@ export default function EditarLaudoPage() { const editorContent = editorRef.current.innerHTML; setContent(editorContent); } + + // Se estiver voltando para o editor, restaurar conteúdo + if (newTab === 'editor') { + setTimeout(() => { + if (editorRef.current && content) { + editorRef.current.innerHTML = content; + } + }, 0); + } + setActiveTab(newTab); }; - // Restaurar conteúdo quando volta para a aba editor - useEffect(() => { - if (activeTab === 'editor' && editorRef.current && content) { - editorRef.current.innerHTML = content; - } - }, [activeTab]); - // Atualizar formatações ativas ao mudar seleção useEffect(() => { const updateFormats = () => { From 8d532271e96808198d7112eda27ee8543781967c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Thu, 27 Nov 2025 22:44:59 -0300 Subject: [PATCH 11/37] fix: report-visual-adjustment --- susconecta/app/laudos-editor/page.tsx | 4 +- susconecta/app/laudos/[id]/editar/page.tsx | 66 +++++++++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/susconecta/app/laudos-editor/page.tsx b/susconecta/app/laudos-editor/page.tsx index bd0eeb9..4b75ec3 100644 --- a/susconecta/app/laudos-editor/page.tsx +++ b/susconecta/app/laudos-editor/page.tsx @@ -499,7 +499,7 @@ export default function LaudosEditorPage() {
diff --git a/susconecta/app/laudos/[id]/editar/page.tsx b/susconecta/app/laudos/[id]/editar/page.tsx index 912d531..d1a4a5d 100644 --- a/susconecta/app/laudos/[id]/editar/page.tsx +++ b/susconecta/app/laudos/[id]/editar/page.tsx @@ -12,6 +12,7 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { FileText, Settings, Eye, ArrowLeft, BookOpen } from 'lucide-react'; export default function EditarLaudoPage() { @@ -29,6 +30,7 @@ export default function EditarLaudoPage() { const [activeTab, setActiveTab] = useState('editor'); const [showPreview, setShowPreview] = useState(false); const [loading, setLoading] = useState(true); + const [showExitDialog, setShowExitDialog] = useState(false); // Campos do laudo const [campos, setCampos] = useState({ @@ -359,7 +361,7 @@ export default function EditarLaudoPage() {
-
+ + {/* Dialog de confirmação de saída */} + + + + Salvar Rascunho? + + Você tem informações não salvas. Deseja salvar como rascunho para continuar depois? + + + + + + + + +
); From c8607556e0ba6f1fca6e54b002236b2e6c03023e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Thu, 27 Nov 2025 23:20:47 -0300 Subject: [PATCH 12/37] fix-pop-up-message --- susconecta/app/laudos-editor/page.tsx | 4 ++-- susconecta/app/laudos/[id]/editar/page.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/susconecta/app/laudos-editor/page.tsx b/susconecta/app/laudos-editor/page.tsx index 4b75ec3..6bf41f7 100644 --- a/susconecta/app/laudos-editor/page.tsx +++ b/susconecta/app/laudos-editor/page.tsx @@ -1016,14 +1016,14 @@ export default function LaudosEditorPage() { setShowDraftConfirm(false); discardDraft(); }} - className="text-xs sm:text-sm h-9 sm:h-10 hover:bg-blue-50 dark:hover:bg-blue-950" + className="text-xs sm:text-sm h-9 sm:h-10 hover:bg-gray-100 dark:hover:bg-gray-800" > Descartar diff --git a/susconecta/app/laudos/[id]/editar/page.tsx b/susconecta/app/laudos/[id]/editar/page.tsx index d1a4a5d..ea6df2a 100644 --- a/susconecta/app/laudos/[id]/editar/page.tsx +++ b/susconecta/app/laudos/[id]/editar/page.tsx @@ -768,7 +768,7 @@ export default function EditarLaudoPage() { setShowExitDialog(false); router.back(); }} - className="w-full sm:w-auto" + className="w-full sm:w-auto hover:bg-gray-100 dark:hover:bg-gray-800" > Descartar @@ -778,7 +778,7 @@ export default function EditarLaudoPage() { setShowExitDialog(false); router.back(); }} - className="w-full sm:w-auto" + className="w-full sm:w-auto hover:bg-gray-100 dark:hover:bg-gray-800" > Voltar From 5251719123bfc6a09fdb63e0c93ba8c171eef7fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Wed, 3 Dec 2025 15:36:30 -0300 Subject: [PATCH 13/37] fix: exception-endpoints --- .../app/(main-routes)/doutores/page.tsx | 9 ++- .../features/forms/exception-form.tsx | 64 ++++++++++++++++++- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/susconecta/app/(main-routes)/doutores/page.tsx b/susconecta/app/(main-routes)/doutores/page.tsx index 2a52829..1b6098d 100644 --- a/susconecta/app/(main-routes)/doutores/page.tsx +++ b/susconecta/app/(main-routes)/doutores/page.tsx @@ -964,7 +964,14 @@ export default function DoutoresPage() { {exceptions.map((ex) => (
-
{ex.date} {ex.start_time ? `• ${ex.start_time}` : ''} {ex.end_time ? `— ${ex.end_time}` : ''}
+
{(() => { + try { + const [y, m, d] = String(ex.date).split('-'); + return `${d}/${m}/${y}`; + } catch (e) { + return ex.date; + } + })()} {ex.start_time ? `• ${ex.start_time}` : ''} {ex.end_time ? `— ${ex.end_time}` : ''}
Tipo: {ex.kind} • Motivo: {ex.reason || '—'}
diff --git a/susconecta/components/features/forms/exception-form.tsx b/susconecta/components/features/forms/exception-form.tsx index b42ca0c..2b81e1d 100644 --- a/susconecta/components/features/forms/exception-form.tsx +++ b/susconecta/components/features/forms/exception-form.tsx @@ -6,6 +6,8 @@ import { Button } from '@/components/ui/button' import { Label } from '@/components/ui/label' import { Input } from '@/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Calendar as CalendarComponent } from '@/components/ui/calendar' +import { Calendar } from 'lucide-react' import { criarExcecao, DoctorExceptionCreate } from '@/lib/api' import { useToast } from '@/hooks/use-toast' @@ -23,6 +25,7 @@ export default function ExceptionForm({ open, onOpenChange, doctorId = null, onS const [kind, setKind] = useState<'bloqueio'|'liberacao'>('bloqueio') const [reason, setReason] = useState('') const [submitting, setSubmitting] = useState(false) + const [showDatePicker, setShowDatePicker] = useState(false) const { toast } = useToast() async function handleSubmit(e?: React.FormEvent) { @@ -67,9 +70,64 @@ export default function ExceptionForm({ open, onOpenChange, doctorId = null, onS
-
- - setDate(e.target.value)} /> +
+
+ + +
+
+ { + try { + const [y, m, d] = String(date).split('-'); + return `${d}/${m}/${y}`; + } catch (e) { + return ''; + } + })() : ''} + readOnly + /> + {showDatePicker && ( +
+ { + try { + const [y, m, d] = String(date).split('-').map(Number); + return new Date(y, m - 1, d); + } catch (e) { + return undefined; + } + })() : undefined} + onSelect={(selectedDate) => { + if (selectedDate) { + const y = selectedDate.getFullYear(); + const m = String(selectedDate.getMonth() + 1).padStart(2, '0'); + const d = String(selectedDate.getDate()).padStart(2, '0'); + const dateStr = `${y}-${m}-${d}`; + setDate(dateStr); + setShowDatePicker(false); + } + }} + disabled={(checkDate) => { + const today = new Date(); + today.setHours(0, 0, 0, 0); + return checkDate < today; + }} + /> +
+ )} +
From 47965fe78a33770fa943c96bec657d13cc619b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Wed, 3 Dec 2025 15:58:48 -0300 Subject: [PATCH 14/37] fix: appoiments-in-admin-page --- .../forms/calendar-registration-form.tsx | 69 ++++++++----------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/susconecta/components/features/forms/calendar-registration-form.tsx b/susconecta/components/features/forms/calendar-registration-form.tsx index d6a415c..eb97fb3 100644 --- a/susconecta/components/features/forms/calendar-registration-form.tsx +++ b/susconecta/components/features/forms/calendar-registration-form.tsx @@ -414,35 +414,31 @@ export function CalendarRegistrationForm({ formData, onFormChange, createMode = } catch (e) {} const generatedSet = new Set(); + + // Helper to create ISO-like string without timezone conversion + const toLocalISOString = (date: Date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; + }; + windows.forEach((w: any) => { try { const perWindowStep = Number(w.slotMinutes) || stepMinutes; const startMs = w.winStart.getTime(); const endMs = w.winEnd.getTime(); const lastStartMs = endMs - perWindowStep * 60000; - const backendSlotsInWindow = (av.slots || []).filter((s: any) => { - try { - const sd = new Date(s.datetime); - const sm = sd.getHours() * 60 + sd.getMinutes(); - const wmStart = w.winStart.getHours() * 60 + w.winStart.getMinutes(); - const wmEnd = w.winEnd.getHours() * 60 + w.winEnd.getMinutes(); - return sm >= wmStart && sm <= wmEnd; - } catch (e) { return false; } - }).map((s: any) => new Date(s.datetime).getTime()).sort((a: number, b: number) => a - b); - - if (!backendSlotsInWindow.length) { - let cursorMs = startMs; - while (cursorMs <= lastStartMs) { - generatedSet.add(new Date(cursorMs).toISOString()); - cursorMs += perWindowStep * 60000; - } - } else { - const lastBackendMs = backendSlotsInWindow[backendSlotsInWindow.length - 1]; - let cursorMs = lastBackendMs + perWindowStep * 60000; - while (cursorMs <= lastStartMs) { - generatedSet.add(new Date(cursorMs).toISOString()); - cursorMs += perWindowStep * 60000; - } + + // Always generate slots from the start of the window to the end + // This ensures slots start at the configured availability start time + let cursorMs = startMs; + while (cursorMs <= lastStartMs) { + generatedSet.add(toLocalISOString(new Date(cursorMs))); + cursorMs += perWindowStep * 60000; } } catch (e) {} }); @@ -463,15 +459,10 @@ export function CalendarRegistrationForm({ formData, onFormChange, createMode = } catch (e) { return null; } }; - (existingInWindow || []).forEach((s: any) => { - const sm = findWindowSlotMinutes(s.datetime); - mergedMap.set(s.datetime, sm ? { ...s, slot_minutes: sm } : { ...s }); - }); + // Use only generated slots based on availability windows Array.from(generatedSet).forEach((dt) => { - if (!mergedMap.has(dt)) { - const sm = findWindowSlotMinutes(dt) || stepMinutes; - mergedMap.set(dt, { datetime: dt, available: true, slot_minutes: sm }); - } + const sm = findWindowSlotMinutes(dt) || stepMinutes; + mergedMap.set(dt, { datetime: dt, available: true, slot_minutes: sm }); }); const merged = Array.from(mergedMap.values()).sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime()); @@ -1071,7 +1062,8 @@ export function CalendarRegistrationForm({ formData, onFormChange, createMode = } const hh = String(dt.getHours()).padStart(2, '0'); const mm = String(dt.getMinutes()).padStart(2, '0'); - const dateOnly = dt.toISOString().split('T')[0]; + // Keep the existing appointmentDate, don't override it + const currentDate = (formData as any).appointmentDate; // set duration from slot if available const sel = (availableSlots || []).find((s) => s.datetime === value) as any; const slotMinutes = sel && sel.slot_minutes ? Number(sel.slot_minutes) : null; @@ -1082,11 +1074,11 @@ export function CalendarRegistrationForm({ formData, onFormChange, createMode = const endM = String(endDt.getMinutes()).padStart(2, '0'); const endStr = `${endH}:${endM}`; if (slotMinutes) { - onFormChange({ ...formData, appointmentDate: dateOnly, startTime: `${hh}:${mm}`, duration_minutes: slotMinutes, endTime: endStr }); + onFormChange({ ...formData, appointmentDate: currentDate, startTime: `${hh}:${mm}`, duration_minutes: slotMinutes, endTime: endStr }); try { setLockedDurationFromSlot(true); } catch (e) {} try { (lastAutoEndRef as any).current = endStr; } catch (e) {} } else { - onFormChange({ ...formData, appointmentDate: dateOnly, startTime: `${hh}:${mm}`, endTime: endStr }); + onFormChange({ ...formData, appointmentDate: currentDate, startTime: `${hh}:${mm}`, endTime: endStr }); try { (lastAutoEndRef as any).current = endStr; } catch (e) {} } } catch (e) { @@ -1188,9 +1180,8 @@ export function CalendarRegistrationForm({ formData, onFormChange, createMode = type="button" className={`h-10 rounded-md border ${formData.startTime === `${hh}:${mm}` ? 'bg-blue-600 text-white' : 'bg-background'}`} onClick={() => { - // when selecting a slot, set appointmentDate (if missing) and startTime and duration - const isoDate = dt.toISOString(); - const dateOnly = isoDate.split('T')[0]; + // when selecting a slot, keep the existing appointmentDate and only update time + const currentDate = (formData as any).appointmentDate; const slotMinutes = s.slot_minutes || null; // compute endTime based on duration const durationForCalc = slotMinutes || (formData as any).duration_minutes || 0; @@ -1199,11 +1190,11 @@ export function CalendarRegistrationForm({ formData, onFormChange, createMode = const endM = String(endDt.getMinutes()).padStart(2, '0'); const endStr = `${endH}:${endM}`; if (slotMinutes) { - onFormChange({ ...formData, appointmentDate: dateOnly, startTime: `${hh}:${mm}`, duration_minutes: Number(slotMinutes), endTime: endStr }); + onFormChange({ ...formData, appointmentDate: currentDate, startTime: `${hh}:${mm}`, duration_minutes: Number(slotMinutes), endTime: endStr }); try { setLockedDurationFromSlot(true); } catch (e) {} try { (lastAutoEndRef as any).current = endStr; } catch (e) {} } else { - onFormChange({ ...formData, appointmentDate: dateOnly, startTime: `${hh}:${mm}`, endTime: endStr }); + onFormChange({ ...formData, appointmentDate: currentDate, startTime: `${hh}:${mm}`, endTime: endStr }); try { (lastAutoEndRef as any).current = endStr; } catch (e) {} } }} From dddbd1e15b56ca3f045119e85d29afd0f0c0312d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Wed, 3 Dec 2025 16:16:33 -0300 Subject: [PATCH 15/37] fix: birth-date-calendar --- .../features/forms/calendar-registration-form.tsx | 6 +++++- .../components/features/forms/doctor-registration-form.tsx | 4 ++++ .../components/features/forms/patient-registration-form.tsx | 4 ++++ susconecta/components/ui/popover.tsx | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/susconecta/components/features/forms/calendar-registration-form.tsx b/susconecta/components/features/forms/calendar-registration-form.tsx index eb97fb3..ba3089c 100644 --- a/susconecta/components/features/forms/calendar-registration-form.tsx +++ b/susconecta/components/features/forms/calendar-registration-form.tsx @@ -1041,7 +1041,11 @@ export function CalendarRegistrationForm({ formData, onFormChange, createMode = const d = new Date(s.datetime); const hh = String(d.getHours()).padStart(2, '0'); const mm = String(d.getMinutes()).padStart(2, '0'); - const dateOnly = d.toISOString().split('T')[0]; + // Use local date components instead of toISOString to avoid timezone conversion + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + const dateOnly = `${year}-${month}-${day}`; return dateOnly === date && `${hh}:${mm}` === time; } catch (e) { return false; diff --git a/susconecta/components/features/forms/doctor-registration-form.tsx b/susconecta/components/features/forms/doctor-registration-form.tsx index 2183868..cf7b95f 100644 --- a/susconecta/components/features/forms/doctor-registration-form.tsx +++ b/susconecta/components/features/forms/doctor-registration-form.tsx @@ -835,6 +835,10 @@ async function handleSubmit(ev: React.FormEvent) { selected={form.data_nascimento ?? undefined} onSelect={(date) => setField("data_nascimento", date ?? null)} initialFocus + captionLayout="dropdown" + fromYear={1900} + toYear={new Date().getFullYear()} + disabled={(date) => date > new Date()} /> diff --git a/susconecta/components/features/forms/patient-registration-form.tsx b/susconecta/components/features/forms/patient-registration-form.tsx index b3b2f21..8a5e739 100644 --- a/susconecta/components/features/forms/patient-registration-form.tsx +++ b/susconecta/components/features/forms/patient-registration-form.tsx @@ -467,6 +467,10 @@ export function PatientRegistrationForm({ selected={form.birth_date ?? undefined} onSelect={(date) => setField("birth_date", date || null)} initialFocus + captionLayout="dropdown" + fromYear={1900} + toYear={new Date().getFullYear()} + disabled={(date) => date > new Date()} /> diff --git a/susconecta/components/ui/popover.tsx b/susconecta/components/ui/popover.tsx index be6014e..a93703f 100644 --- a/susconecta/components/ui/popover.tsx +++ b/susconecta/components/ui/popover.tsx @@ -30,7 +30,7 @@ function PopoverContent({ align={align} sideOffset={sideOffset} className={cn( - "bg-white text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-[9999] w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md", + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-[9999] w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md", className )} {...props} From 4f197aafd5417fd644e3d2e921829c808d550bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Wed, 3 Dec 2025 16:37:20 -0300 Subject: [PATCH 16/37] fix: hover-errors --- .../components/features/forms/doctor-registration-form.tsx | 2 +- .../components/features/forms/patient-registration-form.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/susconecta/components/features/forms/doctor-registration-form.tsx b/susconecta/components/features/forms/doctor-registration-form.tsx index cf7b95f..098b8df 100644 --- a/susconecta/components/features/forms/doctor-registration-form.tsx +++ b/susconecta/components/features/forms/doctor-registration-form.tsx @@ -821,7 +821,7 @@ async function handleSubmit(ev: React.FormEvent) {
- {capitalize(viewingAppointment?.type || "")} + {capitalize(viewingAppointment?.appointment_type || viewingAppointment?.type || "")}
diff --git a/susconecta/app/(main-routes)/doutores/page.tsx b/susconecta/app/(main-routes)/doutores/page.tsx index 1b6098d..72373f0 100644 --- a/susconecta/app/(main-routes)/doutores/page.tsx +++ b/susconecta/app/(main-routes)/doutores/page.tsx @@ -131,6 +131,7 @@ export default function DoutoresPage() { const [availabilityOpenFor, setAvailabilityOpenFor] = useState(null); const [availabilityViewingFor, setAvailabilityViewingFor] = useState(null); const [availabilities, setAvailabilities] = useState([]); + const [availabilitiesForCreate, setAvailabilitiesForCreate] = useState([]); const [availLoading, setAvailLoading] = useState(false); const [editingAvailability, setEditingAvailability] = useState(null); const [exceptions, setExceptions] = useState([]); @@ -633,7 +634,17 @@ export default function DoutoresPage() { Ver pacientes atribuídos - setAvailabilityOpenFor(doctor)}> + { + try { + const list = await listarDisponibilidades({ doctorId: doctor.id, active: true }); + setAvailabilitiesForCreate(list || []); + setAvailabilityOpenFor(doctor); + } catch (e) { + console.warn('Erro ao carregar disponibilidades:', e); + setAvailabilitiesForCreate([]); + setAvailabilityOpenFor(doctor); + } + }}> Criar disponibilidade @@ -869,6 +880,7 @@ export default function DoutoresPage() { open={!!availabilityOpenFor} onOpenChange={(open) => { if (!open) setAvailabilityOpenFor(null); }} doctorId={availabilityOpenFor?.id} + existingAvailabilities={availabilitiesForCreate} onSaved={(saved) => { console.log('Disponibilidade salva', saved); setAvailabilityOpenFor(null); /* optionally reload list */ reloadAvailabilities(availabilityOpenFor?.id); }} /> )} @@ -890,6 +902,7 @@ export default function DoutoresPage() { doctorId={editingAvailability?.doctor_id ?? availabilityViewingFor?.id} availability={editingAvailability} mode="edit" + existingAvailabilities={availabilities} onSaved={(saved) => { console.log('Disponibilidade atualizada', saved); setEditingAvailability(null); reloadAvailabilities(editingAvailability?.doctor_id ?? availabilityViewingFor?.id); }} /> )} @@ -910,14 +923,35 @@ export default function DoutoresPage() {
Carregando disponibilidades…
) : availabilities && availabilities.length ? (
- {availabilities.map((a) => ( + {availabilities + .sort((a, b) => { + // Define a ordem dos dias da semana (Segunda a Domingo) + const weekdayOrder: Record = { + 'segunda': 1, 'segunda-feira': 1, 'mon': 1, 'monday': 1, '1': 1, + 'terca': 2, 'terça': 2, 'terça-feira': 2, 'tue': 2, 'tuesday': 2, '2': 2, + 'quarta': 3, 'quarta-feira': 3, 'wed': 3, 'wednesday': 3, '3': 3, + 'quinta': 4, 'quinta-feira': 4, 'thu': 4, 'thursday': 4, '4': 4, + 'sexta': 5, 'sexta-feira': 5, 'fri': 5, 'friday': 5, '5': 5, + 'sabado': 6, 'sábado': 6, 'sat': 6, 'saturday': 6, '6': 6, + 'domingo': 7, 'dom': 7, 'sun': 7, 'sunday': 7, '0': 7, '7': 7 + }; + + const getWeekdayOrder = (weekday: any) => { + if (typeof weekday === 'number') return weekday === 0 ? 7 : weekday; + const normalized = String(weekday).toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, ''); + return weekdayOrder[normalized] || 999; + }; + + return getWeekdayOrder(a.weekday) - getWeekdayOrder(b.weekday); + }) + .map((a) => (
{translateWeekday(a.weekday)} • {a.start_time} — {a.end_time}
Duração: {a.slot_minutes} min • Tipo: {a.appointment_type || '—'} • {a.active ? 'Ativa' : 'Inativa'}
- + - + From 4b9f0695f2c2ad118a59387870eb8daa9eba7f1f Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Wed, 3 Dec 2025 21:19:06 -0300 Subject: [PATCH 20/37] feat: addavailability and exceptions on the doctor's page --- susconecta/app/profissional/page.tsx | 240 +++++++++++++++++- .../features/forms/exception-form.tsx | 22 +- susconecta/lib/api.ts | 22 +- 3 files changed, 270 insertions(+), 14 deletions(-) diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx index 00a9662..0138a6a 100644 --- a/susconecta/app/profissional/page.tsx +++ b/susconecta/app/profissional/page.tsx @@ -9,7 +9,7 @@ import { useAuth } from "@/hooks/useAuth"; import { useToast } from "@/hooks/use-toast"; import { useAvatarUrl } from "@/hooks/useAvatarUrl"; import { UploadAvatar } from '@/components/ui/upload-avatar'; -import { buscarPacientes, listarPacientes, buscarPacientePorId, buscarPacientesPorIds, buscarMedicoPorId, buscarMedicosPorIds, buscarMedicos, listarAgendamentos, type Paciente, buscarRelatorioPorId, atualizarMedico } from "@/lib/api"; +import { buscarPacientes, listarPacientes, buscarPacientePorId, buscarPacientesPorIds, buscarMedicoPorId, buscarMedicosPorIds, buscarMedicos, listarAgendamentos, type Paciente, buscarRelatorioPorId, atualizarMedico, listarDisponibilidades, DoctorAvailability, deletarDisponibilidade, listarExcecoes, DoctorException, deletarExcecao } from "@/lib/api"; import { ENV_CONFIG } from '@/lib/env-config'; import { useReports } from "@/hooks/useReports"; import { CreateReportData } from "@/types/report-types"; @@ -19,6 +19,8 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from "@/components/ui/select"; +import AvailabilityForm from '@/components/features/forms/availability-form'; +import ExceptionForm from '@/components/features/forms/exception-form'; import { SimpleThemeToggle } from "@/components/ui/simple-theme-toggle"; import { Table, @@ -65,6 +67,29 @@ const colorsByType = { Oftalmologia: "#2ecc71" }; + // Função para traduzir dias da semana + function translateWeekday(w?: string) { + if (!w) return ''; + const key = w.toString().toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '').replace(/[^a-z0-9]/g, ''); + const map: Record = { + 'segunda': 'Segunda', + 'terca': 'Terça', + 'quarta': 'Quarta', + 'quinta': 'Quinta', + 'sexta': 'Sexta', + 'sabado': 'Sábado', + 'domingo': 'Domingo', + 'monday': 'Segunda', + 'tuesday': 'Terça', + 'wednesday': 'Quarta', + 'thursday': 'Quinta', + 'friday': 'Sexta', + 'saturday': 'Sábado', + 'sunday': 'Domingo', + }; + return map[key] ?? w; + } + // Helpers para normalizar dados de paciente (suporta schema antigo e novo) const getPatientName = (p: any) => p?.full_name ?? p?.nome ?? ''; const getPatientCpf = (p: any) => p?.cpf ?? ''; @@ -132,6 +157,16 @@ const ProfissionalPage = () => { const [isEditingProfile, setIsEditingProfile] = useState(false); const [doctorId, setDoctorId] = useState(null); + // Estados para disponibilidades e exceções do médico logado + const [availabilities, setAvailabilities] = useState([]); + const [exceptions, setExceptions] = useState([]); + const [availLoading, setAvailLoading] = useState(false); + const [exceptLoading, setExceptLoading] = useState(false); + const [editingAvailability, setEditingAvailability] = useState(null); + const [editingException, setEditingException] = useState(null); + const [showAvailabilityForm, setShowAvailabilityForm] = useState(false); + const [showExceptionForm, setShowExceptionForm] = useState(false); + // Hook para carregar automaticamente o avatar do médico const { avatarUrl: retrievedAvatarUrl } = useAvatarUrl(doctorId); // Removemos o placeholder extenso — inicializamos com valores minimalistas e vazios. @@ -286,6 +321,48 @@ const ProfissionalPage = () => { } }, [retrievedAvatarUrl]); + // Carregar disponibilidades e exceções do médico logado + const reloadAvailabilities = async (medId?: string) => { + const id = medId || doctorId; + if (!id) return; + try { + setAvailLoading(true); + const avails = await listarDisponibilidades({ doctorId: id, active: true }); + setAvailabilities(Array.isArray(avails) ? avails : []); + } catch (e) { + console.warn('[ProfissionalPage] Erro ao carregar disponibilidades:', e); + setAvailabilities([]); + } finally { + setAvailLoading(false); + } + }; + + const reloadExceptions = async (medId?: string) => { + const id = medId || doctorId; + if (!id) return; + try { + setExceptLoading(true); + console.log('[ProfissionalPage] Recarregando exceções para médico:', id); + const excepts = await listarExcecoes({ doctorId: id }); + console.log('[ProfissionalPage] Exceções carregadas:', excepts); + setExceptions(Array.isArray(excepts) ? excepts : []); + } catch (e) { + console.warn('[ProfissionalPage] Erro ao carregar exceções:', e); + setExceptions([]); + } finally { + setExceptLoading(false); + } + }; + + // Carrega disponibilidades quando doctorId muda + useEffect(() => { + if (doctorId) { + reloadAvailabilities(doctorId); + reloadExceptions(doctorId); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [doctorId]); + // Estados para campos principais da consulta const [consultaAtual, setConsultaAtual] = useState({ @@ -2746,7 +2823,129 @@ const ProfissionalPage = () => {
); - + const renderDisponibilidadesSection = () => { + // Filtrar apenas a primeira disponibilidade de cada dia da semana + const availabilityByDay = new Map(); + (availabilities || []).forEach((a) => { + const day = String(a.weekday ?? '').toLowerCase(); + if (!availabilityByDay.has(day)) { + availabilityByDay.set(day, a); + } + }); + const filteredAvailabilities = Array.from(availabilityByDay.values()); + + // Filtrar apenas a primeira exceção de cada data + const exceptionByDate = new Map(); + (exceptions || []).forEach((ex) => { + const date = String(ex.exception_date ?? ex.date ?? ''); + if (!exceptionByDate.has(date)) { + exceptionByDate.set(date, ex); + } + }); + const filteredExceptions = Array.from(exceptionByDate.values()); + + return ( +
+
+

Minhas Disponibilidades

+
+ +
+
+ + {/* Disponibilidades */} + {availLoading ? ( +
Carregando disponibilidades…
+ ) : filteredAvailabilities && filteredAvailabilities.length > 0 ? ( +
+ {filteredAvailabilities.map((a) => ( +
+
+
{translateWeekday(a.weekday)} • {a.start_time} — {a.end_time}
+
Duração: {a.slot_minutes} min • Tipo: {a.appointment_type || '—'} • {a.active ? 'Ativa' : 'Inativa'}
+
+
+ + +
+
+ ))} +
+ ) : ( +
+ Nenhuma disponibilidade cadastrada. +
+ )} + + {/* Exceções */} +
+

Exceções (Bloqueios/Liberações)

+ {exceptLoading ? ( +
Carregando exceções…
+ ) : filteredExceptions && filteredExceptions.length > 0 ? ( +
+ {filteredExceptions.map((ex) => ( +
+
+
+ {new Date(ex.exception_date ?? ex.date).toLocaleDateString('pt-BR')} +
+
+ Tipo: bloqueio • Motivo: {ex.reason || '—'} +
+
+
+ {/* Sem ações para exceções */} +
+
+ ))} +
+ ) : ( +
+ Nenhuma exceção cadastrada. +
+ )} +
+
+ ); + }; + const renderPerfilSection = () => (
{/* Header com Título e Botão */} @@ -3022,6 +3221,8 @@ const ProfissionalPage = () => { ); case 'laudos': return renderLaudosSection(); + case 'disponibilidades': + return renderDisponibilidadesSection(); case 'comunicacao': return renderComunicacaoSection(); case 'perfil': @@ -3158,6 +3359,17 @@ const ProfissionalPage = () => { Laudos +
- {} + {/* AvailabilityForm para criar/editar disponibilidades */} + {showAvailabilityForm && ( + { + if (!open) { + setShowAvailabilityForm(false); + setEditingAvailability(null); + } + }} + doctorId={editingAvailability?.doctor_id ?? doctorId} + availability={editingAvailability} + mode={editingAvailability ? "edit" : "create"} + onSaved={(saved) => { + console.log('Disponibilidade salva', saved); + setEditingAvailability(null); + setShowAvailabilityForm(false); + reloadAvailabilities(); + }} + /> + )} + + {/* Popup antigo (manter para compatibilidade) */} {showPopup && (
diff --git a/susconecta/components/features/forms/exception-form.tsx b/susconecta/components/features/forms/exception-form.tsx index 2b81e1d..063363d 100644 --- a/susconecta/components/features/forms/exception-form.tsx +++ b/susconecta/components/features/forms/exception-form.tsx @@ -28,6 +28,19 @@ export default function ExceptionForm({ open, onOpenChange, doctorId = null, onS const [showDatePicker, setShowDatePicker] = useState(false) const { toast } = useToast() + // Resetar form quando dialog fecha + const handleOpenChange = (newOpen: boolean) => { + if (!newOpen) { + setDate('') + setStartTime('') + setEndTime('') + setKind('bloqueio') + setReason('') + setShowDatePicker(false) + } + onOpenChange(newOpen) + } + async function handleSubmit(e?: React.FormEvent) { e?.preventDefault() if (!doctorId) { @@ -53,7 +66,7 @@ export default function ExceptionForm({ open, onOpenChange, doctorId = null, onS const saved = await criarExcecao(payload) toast({ title: 'Exceção criada', description: `${payload.date} • ${kind}`, variant: 'default' }) onSaved?.(saved) - onOpenChange(false) + handleOpenChange(false) } catch (err: any) { console.error('Erro ao criar exceção:', err) toast({ title: 'Erro', description: err?.message || String(err), variant: 'destructive' }) @@ -63,7 +76,7 @@ export default function ExceptionForm({ open, onOpenChange, doctorId = null, onS } return ( - + Criar exceção @@ -103,6 +116,7 @@ export default function ExceptionForm({ open, onOpenChange, doctorId = null, onS mode="single" selected={date ? (() => { try { + // Parse como local date para compatibilidade com Calendar const [y, m, d] = String(date).split('-').map(Number); return new Date(y, m - 1, d); } catch (e) { @@ -111,10 +125,12 @@ export default function ExceptionForm({ open, onOpenChange, doctorId = null, onS })() : undefined} onSelect={(selectedDate) => { if (selectedDate) { + // Extrair data como local para evitar problemas de timezone const y = selectedDate.getFullYear(); const m = String(selectedDate.getMonth() + 1).padStart(2, '0'); const d = String(selectedDate.getDate()).padStart(2, '0'); const dateStr = `${y}-${m}-${d}`; + console.log('[ExceptionForm] Data selecionada:', dateStr, 'de', selectedDate); setDate(dateStr); setShowDatePicker(false); } @@ -160,7 +176,7 @@ export default function ExceptionForm({ open, onOpenChange, doctorId = null, onS
- + diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index 7846058..349c5f6 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -488,11 +488,10 @@ export async function deletarDisponibilidade(id: string): Promise { headers: withPrefer({ ...baseHeaders() }, 'return=minimal'), }); - if (res.status === 204) return; - // Some deployments may return 200 with a representation — accept that too - if (res.status === 200) return; - // Otherwise surface a friendly error using parse() - await parse(res as Response); + if (res.status === 204 || res.status === 200) return; + + // Se chegou aqui e não foi sucesso, lance erro + throw new Error(`Erro ao deletar disponibilidade: ${res.status}`); } // ===== EXCEÇÕES (Doctor Exceptions) ===== @@ -580,14 +579,21 @@ export async function listarExcecoes(params?: { doctorId?: string; date?: string export async function deletarExcecao(id: string): Promise { if (!id) throw new Error('ID da exceção é obrigatório'); const url = `${REST}/doctor_exceptions?id=eq.${encodeURIComponent(String(id))}`; + console.log('[deletarExcecao] Deletando exceção:', id, 'URL:', url); const res = await fetch(url, { method: 'DELETE', headers: withPrefer({ ...baseHeaders() }, 'return=minimal'), }); - if (res.status === 204) return; - if (res.status === 200) return; - await parse(res as Response); + console.log('[deletarExcecao] Status da resposta:', res.status); + + if (res.status === 204 || res.status === 200) { + console.log('[deletarExcecao] Exceção deletada com sucesso'); + return; + } + + // Se chegou aqui e não foi sucesso, lance erro + throw new Error(`Erro ao deletar exceção: ${res.status}`); } From 57310f6621c79fa5a9d46e1508ab387a58031e92 Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Wed, 3 Dec 2025 21:31:34 -0300 Subject: [PATCH 21/37] fix(profissional): Standardized the creation of physician availability on the physician's page --- susconecta/app/profissional/page.tsx | 57 ++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx index 038978c..4a29a85 100644 --- a/susconecta/app/profissional/page.tsx +++ b/susconecta/app/profissional/page.tsx @@ -160,6 +160,7 @@ const ProfissionalPage = () => { // Estados para disponibilidades e exceções do médico logado const [availabilities, setAvailabilities] = useState([]); const [exceptions, setExceptions] = useState([]); + const [availabilitiesForCreate, setAvailabilitiesForCreate] = useState([]); const [availLoading, setAvailLoading] = useState(false); const [exceptLoading, setExceptLoading] = useState(false); const [editingAvailability, setEditingAvailability] = useState(null); @@ -2842,7 +2843,28 @@ const ProfissionalPage = () => { availabilityByDay.set(day, a); } }); - const filteredAvailabilities = Array.from(availabilityByDay.values()); + let filteredAvailabilities = Array.from(availabilityByDay.values()); + + // Ordenar por dia da semana (Segunda a Domingo) + filteredAvailabilities = filteredAvailabilities.sort((a, b) => { + const weekdayOrder: Record = { + 'segunda': 1, 'segunda-feira': 1, 'mon': 1, 'monday': 1, '1': 1, + 'terca': 2, 'terça': 2, 'terça-feira': 2, 'tue': 2, 'tuesday': 2, '2': 2, + 'quarta': 3, 'quarta-feira': 3, 'wed': 3, 'wednesday': 3, '3': 3, + 'quinta': 4, 'quinta-feira': 4, 'thu': 4, 'thursday': 4, '4': 4, + 'sexta': 5, 'sexta-feira': 5, 'fri': 5, 'friday': 5, '5': 5, + 'sabado': 6, 'sábado': 6, 'sat': 6, 'saturday': 6, '6': 6, + 'domingo': 7, 'dom': 7, 'sun': 7, 'sunday': 7, '0': 7, '7': 7 + }; + + const getWeekdayOrder = (weekday: any) => { + if (typeof weekday === 'number') return weekday === 0 ? 7 : weekday; + const normalized = String(weekday).toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, ''); + return weekdayOrder[normalized] || 999; + }; + + return getWeekdayOrder(a.weekday) - getWeekdayOrder(b.weekday); + }); // Filtrar apenas a primeira exceção de cada data const exceptionByDate = new Map(); @@ -2862,9 +2884,18 @@ const ProfissionalPage = () => { -
From e5304894b4e8eba3bd19009c283d506553313994 Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Wed, 3 Dec 2025 21:54:17 -0300 Subject: [PATCH 24/37] fix(readme): remove emojis --- README.md | 110 +++++++++++++++++++++++++++--------------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 4beccf8..a737796 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ --- -## 📋 Índice +## Índice 1. [Visão Geral](#-visão-geral) 2. [Problema e Solução](#-problema-e-solução) @@ -32,61 +32,61 @@ --- -## 🎯 Visão Geral +## Visão Geral **MEDIConnect** é uma plataforma web moderna e intuitiva desenvolvida para revolucionar a gestão de saúde em clínicas e hospitais. Com foco na redução do absenteísmo (faltas em consultas), a plataforma oferece uma experiência completa para pacientes, profissionais de saúde e administradores. -### 🌟 Diferenciais +### Diferenciais -- 🤖 **Zoe IA Assistant**: Assistente virtual inteligente para suporte aos usuários -- 📱 **Interface Responsiva**: Design moderno e adaptável a qualquer dispositivo -- 🔐 **Autenticação Segura**: Sistema robusto com perfis diferenciados -- ⚡ **Performance**: Construído com Next.js 15 para máxima velocidade -- 🎨 **UX/UI Premium**: Interface limpa e profissional voltada para área da saúde +- **Zoe IA Assistant**: Assistente virtual inteligente para suporte aos usuários +- **Interface Responsiva**: Design moderno e adaptável a qualquer dispositivo +- **Autenticação Segura**: Sistema robusto com perfis diferenciados +- **Performance**: Construído com Next.js 15 para máxima velocidade +- **UX/UI Premium**: Interface limpa e profissional voltada para área da saúde --- -## 🩺 Problema e Solução +## Problema e Solução ### O Problema O **absenteísmo** (não comparecimento a consultas agendadas) é um problema crítico em clínicas e hospitais, causando: -- ⏰ Desperdício de tempo dos profissionais -- 💰 Perda de receita para estabelecimentos -- 📉 Redução da eficiência operacional -- 😔 Impacto negativo no atendimento de outros pacientes +- Desperdício de tempo dos profissionais +- Perda de receita para estabelecimentos +- Redução da eficiência operacional +- Impacto negativo no atendimento de outros pacientes ### Nossa Solução MEDIConnect oferece um sistema inteligente de gestão que: -- ✅ Facilita o agendamento e reagendamento de consultas -- ✅ Permite visualização clara da agenda para profissionais -- ✅ Oferece assistência via IA para dúvidas e suporte +- Facilita o agendamento e reagendamento de consultas +- Permite visualização clara da agenda para profissionais +- Oferece assistência via IA para dúvidas e suporte --- -## ✨ Funcionalidades +## Funcionalidades -### 👤 Para Pacientes -- 🏠 **Dashboard Personalizado**: Visão geral de consultas e exames -- 📅 **Agendamento**: Sistema fácil de marcar consultas -- 📋 **Resultados de Exames**: Acesso seguro a laudos e resultados -- 👨‍⚕️ **Busca de Profissionais**: Encontre médicos por especialidade -- 💬 **Zoe IA Assistant**: Tire dúvidas 24/7 com nossa assistente virtual +### Para Pacientes +- **Dashboard Personalizado**: Visão geral de consultas e exames +- **Agendamento**: Sistema fácil de marcar consultas +- **Resultados de Exames**: Acesso seguro a laudos e resultados +- **Busca de Profissionais**: Encontre médicos por especialidade +- **Zoe IA Assistant**: Tire dúvidas 24/7 com nossa assistente virtual -### 👨‍⚕️ Para Profissionais -- 📊 **Dashboard Profissional**: Visão completa de atendimentos -- ✍️ **Editor de Laudos**: Crie e edite laudos médicos de forma rápida -- 👥 **Gestão de Pacientes**: Acesse informações dos pacientes -- 📈 **Agenda**: Visualização clara de consultas +### Para Profissionais +- **Dashboard Profissional**: Visão completa de atendimentos +- **Editor de Laudos**: Crie e edite laudos médicos de forma rápida +- **Gestão de Pacientes**: Acesse informações dos pacientes +- **Agenda**: Visualização clara de consultas -### 🔧 Para Administradores -- 📊 **Dashboard Administrativo**: Métricas e estatísticas em tempo real -- 📈 **Relatórios Detalhados**: Análise de comparecimento e absenteísmo -- 👥 **Gestão Completa**: Gerencie pacientes, profissionais e agendamentos -- 🎯 **Painel de Controle**: Visão 360° da operação da clínica +### Para Administradores +- **Dashboard Administrativo**: Métricas e estatísticas em tempo real +- **Relatórios Detalhados**: Análise de comparecimento e absenteísmo +- **Gestão Completa**: Gerencie pacientes, profissionais e agendamentos +- **Painel de Controle**: Visão 360° da operação da clínica --- -## 🛠️ Tecnologias +## Tecnologias ### Frontend (Atual) - **[Next.js 15](https://nextjs.org/)** - Framework React com Server Components @@ -111,7 +111,7 @@ MEDIConnect oferece um sistema inteligente de gestão que: --- -## 🚀 Instalação +## Instalação ### Pré-requisitos @@ -167,21 +167,21 @@ Abra [http://localhost:3000](http://localhost:3000) no seu navegador. --- -## 💻 Como Usar +## Como Usar ### Navegação Principal -#### 🏠 Página Inicial +#### Página Inicial Acesse `/home` para conhecer a plataforma e suas funcionalidades. -#### 🔐 Autenticação +#### Autenticação O sistema possui três níveis de acesso: - **Pacientes**: `/login-paciente` - **Profissionais**: `/login-profissional` - **Administradores**: `/login-admin` -#### 📱 Funcionalidades por Perfil +#### Funcionalidades por Perfil **Como Paciente:** 1. Faça login em `/login-paciente` @@ -205,7 +205,7 @@ O sistema possui três níveis de acesso: --- -## 🎭 Fluxos de Usuário +## Fluxos de Usuário ### Fluxo de Agendamento (Paciente) @@ -243,9 +243,9 @@ E --> F[Tomar Decisões] --- -## 🧩 Componentes Principais +## Componentes Principais -### 🤖 Zoe IA Assistant +### Zoe IA Assistant Assistente virtual inteligente que oferece: - Suporte 24/7 aos usuários @@ -258,7 +258,7 @@ Assistente virtual inteligente que oferece: - `components/ZoeIA/voice-powered-orb.tsx` - `components/ZoeIA/demo.tsx` -### 📅 Sistema de Agendamento +### Sistema de Agendamento Gerenciamento completo de consultas e exames: - Calendário interativo @@ -271,7 +271,7 @@ Gerenciamento completo de consultas e exames: - `components/features/Calendario/` - `app/(main-routes)/consultas/` -### 📋 Editor de Laudos +### Editor de Laudos Ferramenta profissional para criação de laudos médicos: - Interface intuitiva @@ -283,7 +283,7 @@ Ferramenta profissional para criação de laudos médicos: - `lib/laudo-exemplos.ts` - `lib/laudo-notification.ts` -### 📊 Dashboard Analytics +### Dashboard Analytics Painéis administrativos com: - Métricas em tempo real @@ -298,7 +298,7 @@ Painéis administrativos com: --- -## 🤝 Contribuindo +## Contribuindo Contribuições são bem-vindas! Siga estes passos: @@ -351,26 +351,26 @@ Descreva suas mudanças detalhadamente. --- -## 📝 Licença +## Licença Este projeto está sob a licença **MIT**. Veja o arquivo [LICENSE](LICENSE) para mais detalhes. -## 📞 Contato +## Contato **MEDIConnect Team** -- 🌐 Website: [mediconnect.com](https://mediconecta-app-liart.vercel.app/) -- 📧 Email dos Desenvolvedores: - - 📧 [Jonas Francisco](mailto:jonastom478@gmail.com) - - 📧 [João Gustavo](mailto:jgcmendonca@gmail.com) - - 📧 [Maria Gabrielly](mailto:maria.gabrielly221106@gmail.com) - - 📧 [Pedro Gomes](mailto:pedrogomes5913@gmail.com) +- Website: [mediconnect.com](https://mediconecta-app-liart.vercel.app/) +- Email dos Desenvolvedores: + - [Jonas Francisco](mailto:jonastom478@gmail.com) + - [João Gustavo](mailto:jgcmendonca@gmail.com) + - [Maria Gabrielly](mailto:maria.gabrielly221106@gmail.com) + - [Pedro Gomes](mailto:pedrogomes5913@gmail.com) ---
-**Desenvolvido com ❤️ pelo squad 20** +**Desenvolvido pelo squad 20** *Transformando a gestão de saúde através da tecnologia* From ead38e1132b4c1c6f89dee7563b5afe4bd8ae6b9 Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Wed, 3 Dec 2025 21:57:32 -0300 Subject: [PATCH 25/37] fix(profissional): correction of dates in exceptions --- susconecta/app/profissional/page.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx index 15fd7fc..3fc927d 100644 --- a/susconecta/app/profissional/page.tsx +++ b/susconecta/app/profissional/page.tsx @@ -3006,10 +3006,17 @@ const ProfissionalPage = () => {
- {new Date(ex.exception_date ?? ex.date).toLocaleDateString('pt-BR')} + {(() => { + try { + const [y, m, d] = String(ex.exception_date ?? ex.date).split('-'); + return `${d}/${m}/${y}`; + } catch (e) { + return ex.exception_date ?? ex.date; + } + })()}
- Tipo: bloqueio • Motivo: {ex.reason || '—'} + Tipo: {ex.kind || 'bloqueio'} • Motivo: {ex.reason || '—'}
From a83dc5e347358181828244f466b79f53c3d7b70b Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Wed, 3 Dec 2025 22:34:51 -0300 Subject: [PATCH 26/37] fix: fix hovers in patient --- susconecta/app/paciente/page.tsx | 36 +++++++++---------- .../paciente/resultados/ResultadosClient.tsx | 8 ++--- susconecta/components/ui/button.tsx | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx index 2a742ab..1e7bb6a 100644 --- a/susconecta/app/paciente/page.tsx +++ b/susconecta/app/paciente/page.tsx @@ -1762,7 +1762,7 @@ export default function PacientePage() {
{/* Grid de 3 colunas (2 + 1) */} -
+
{/* Coluna Esquerda - Informações Pessoais */}
{/* Informações Pessoais */} @@ -1925,35 +1925,35 @@ export default function PacientePage() {
{/* Header com informações do paciente */} -
-
+
+
{/* Logo MEDIConnect */} -
-
+
+
- + MEDIConnect
-
+
- + - {profileData.nome?.charAt(0) || 'P'} + {profileData.nome?.charAt(0) || 'P'} -
- Conta do paciente - {profileData.nome || 'Paciente'} - {profileData.email || 'Email não disponível'} +
+ Conta do paciente + {profileData.nome || 'Paciente'} + {profileData.email || 'Email não disponível'}
-
+
-
diff --git a/susconecta/app/paciente/resultados/ResultadosClient.tsx b/susconecta/app/paciente/resultados/ResultadosClient.tsx index 10dd745..68c4007 100644 --- a/susconecta/app/paciente/resultados/ResultadosClient.tsx +++ b/susconecta/app/paciente/resultados/ResultadosClient.tsx @@ -1030,11 +1030,11 @@ export default function ResultadosClient() {
- - + + Página {currentPage} de {totalPages} - - + +
)} diff --git a/susconecta/components/ui/button.tsx b/susconecta/components/ui/button.tsx index a2df8dc..601faae 100644 --- a/susconecta/components/ui/button.tsx +++ b/susconecta/components/ui/button.tsx @@ -14,7 +14,7 @@ const buttonVariants = cva( destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + "border bg-background shadow-xs hover:bg-blue-500 hover:text-white dark:bg-input/30 dark:border-input dark:hover:bg-blue-600 dark:hover:text-white", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: From 0eb7fd7171e71b51fdfe2666b2c6ab06244ff9e2 Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Wed, 3 Dec 2025 22:40:47 -0300 Subject: [PATCH 27/37] fix: responsiveness of the "Foto do Perfil" card profile page --- susconecta/app/paciente/page.tsx | 2 +- susconecta/components/ui/upload-avatar.tsx | 28 ++++++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx index 1e7bb6a..a3e7245 100644 --- a/susconecta/app/paciente/page.tsx +++ b/susconecta/app/paciente/page.tsx @@ -1889,7 +1889,7 @@ export default function PacientePage() {

Foto do Perfil

{isEditingProfile ? ( -
+
-
- +
+
+ {initials} +
-
-
+
+
+
{currentAvatarUrl && ( @@ -101,10 +103,10 @@ export function UploadAvatar({ userId, currentAvatarUrl, onAvatarChange, userNam variant="outline" size="sm" onClick={handleDownload} - className="transition duration-200 hover:bg-primary/10 hover:text-primary dark:hover:bg-accent dark:hover:text-accent-foreground" + className="transition duration-200 hover:bg-blue-500 hover:text-white dark:hover:bg-blue-600 dark:hover:text-white text-xs sm:text-sm" > - - Download + + Download )}
@@ -118,8 +120,8 @@ export function UploadAvatar({ userId, currentAvatarUrl, onAvatarChange, userNam disabled={isUploading} /> -

- Formatos aceitos: JPG, PNG, WebP (máx. 2MB) +

+ Formatos: JPG, PNG, WebP (máx. 2MB)

{error && ( From 171c954a78588a07e55abe29d8bcfccb4f2023b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Wed, 3 Dec 2025 23:37:51 -0300 Subject: [PATCH 28/37] fix: image-report --- susconecta/app/laudos-editor/page.tsx | 53 ------------ susconecta/app/profissional/page.tsx | 119 +++----------------------- 2 files changed, 14 insertions(+), 158 deletions(-) diff --git a/susconecta/app/laudos-editor/page.tsx b/susconecta/app/laudos-editor/page.tsx index 6bf41f7..bc68421 100644 --- a/susconecta/app/laudos-editor/page.tsx +++ b/susconecta/app/laudos-editor/page.tsx @@ -604,17 +604,6 @@ export default function LaudosEditorPage() { Editor -
)} - {/* Imagens Tab */} - {activeTab === 'imagens' && ( -
-
- - -
- -
- {imagens.map((img) => ( -
- {img.type.startsWith('image/') ? ( - {img.name} - ) : ( -
- -
- )} -

{img.name}

- -
- ))} -
-
- )} - {/* Campos Tab */} {activeTab === 'campos' && (
diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx index 3fc927d..ab84d41 100644 --- a/susconecta/app/profissional/page.tsx +++ b/susconecta/app/profissional/page.tsx @@ -2311,32 +2311,6 @@ const ProfissionalPage = () => { Editor -
)} - {activeTab === "imagens" && ( -
-
- - -
- -
- {imagens.map((img) => ( -
- {img.type.startsWith('image/') ? ( - // eslint-disable-next-line @next/next/no-img-element - {img.name} - ) : ( -
- -
- )} -

{img.name}

- -
- ))} -
-
- )} - {activeTab === "campos" && (
@@ -3234,42 +3164,21 @@ const ProfissionalPage = () => {

Foto do Perfil

- {isEditingProfile ? ( - { - try { - setProfileData((prev) => ({ ...prev, fotoUrl: newUrl })); - // Foto foi salva no Supabase Storage - atualizar apenas o estado local - // Para persistir no banco, o usuário deve clicar em "Salvar" após isso - try { toast({ title: 'Foto enviada', description: 'Clique em "Salvar" para confirmar as alterações.', variant: 'default' }); } catch (e) { /* ignore toast errors */ } - } catch (err) { - console.error('[ProfissionalPage] erro ao processar upload de foto:', err); - try { toast({ title: 'Erro ao processar foto', description: (err as any)?.message || 'Falha ao processar a foto do perfil.', variant: 'destructive' }); } catch (e) {} - } - }} - /> - ) : ( - <> - - {(profileData as any).fotoUrl ? ( - - ) : ( - - {profileData.nome?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) || 'MD'} - - )} - + + {(profileData as any).fotoUrl ? ( + + ) : ( + + {profileData.nome?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) || 'MD'} + + )} + -
-

- {profileData.nome?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) || 'MD'} -

-
- - )} +
+

+ {profileData.nome?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) || 'MD'} +

+
From 62cced521f4704065e207515781645b3ce7a3adb Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Wed, 3 Dec 2025 22:47:08 -0300 Subject: [PATCH 29/37] fix(paciente):removed the option to edit patient photos --- susconecta/app/paciente/page.tsx | 35 +++++++++++--------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx index a3e7245..f344784 100644 --- a/susconecta/app/paciente/page.tsx +++ b/susconecta/app/paciente/page.tsx @@ -1888,31 +1888,20 @@ export default function PacientePage() {

Foto do Perfil

- {isEditingProfile ? ( -
- handleProfileChange('foto_url', newUrl)} - userName={profileData.nome} - /> -
- ) : ( -
- - - - {profileData.nome?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) || 'PC'} - - +
+ + + + {profileData.nome?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) || 'PC'} + + -
-

- {profileData.nome?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) || 'PC'} -

-
+
+

+ {profileData.nome?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) || 'PC'} +

- )} +
From 5858886efd089beadd35308886d92e13751d2d25 Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Wed, 3 Dec 2025 22:50:30 -0300 Subject: [PATCH 30/37] fix(dashboard):removed the display of pending reports from the administrator/secretary --- .../app/(main-routes)/dashboard/page.tsx | 46 ++----------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/susconecta/app/(main-routes)/dashboard/page.tsx b/susconecta/app/(main-routes)/dashboard/page.tsx index 67056ab..1f953f6 100644 --- a/susconecta/app/(main-routes)/dashboard/page.tsx +++ b/susconecta/app/(main-routes)/dashboard/page.tsx @@ -9,7 +9,6 @@ import { getUpcomingAppointments, getAppointmentsByDateRange, getNewUsersLastDays, - getPendingReports, getDisabledUsers, getDoctorsAvailabilityToday, getPatientById, @@ -18,7 +17,7 @@ import { import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Alert, AlertDescription } from '@/components/ui/alert'; -import { AlertCircle, Calendar, Users, Stethoscope, Clock, FileText, AlertTriangle, Plus, ArrowLeft } from 'lucide-react'; +import { AlertCircle, Calendar, Users, Stethoscope, Clock, AlertTriangle, Plus, ArrowLeft } from 'lucide-react'; import Link from 'next/link'; import { PatientRegistrationForm } from '@/components/features/forms/patient-registration-form'; import { DoctorRegistrationForm } from '@/components/features/forms/doctor-registration-form'; @@ -49,7 +48,6 @@ export default function DashboardPage() { const [appointments, setAppointments] = useState([]); const [appointmentData, setAppointmentData] = useState([]); const [newUsers, setNewUsers] = useState([]); - const [pendingReports, setPendingReports] = useState([]); const [disabledUsers, setDisabledUsers] = useState([]); const [doctors, setDoctors] = useState>(new Map()); const [patients, setPatients] = useState>(new Map()); @@ -83,18 +81,16 @@ export default function DashboardPage() { }); // 2. Carrega dados dos widgets em paralelo - const [upcomingAppts, appointmentDataRange, newUsersList, pendingReportsList, disabledUsersList] = await Promise.all([ + const [upcomingAppts, appointmentDataRange, newUsersList, disabledUsersList] = await Promise.all([ getUpcomingAppointments(5), getAppointmentsByDateRange(7), getNewUsersLastDays(7), - getPendingReports(5), getDisabledUsers(5), ]); setAppointments(upcomingAppts); setAppointmentData(appointmentDataRange); setNewUsers(newUsersList); - setPendingReports(pendingReportsList); setDisabledUsers(disabledUsersList); // 3. Busca detalhes de pacientes e médicos para as próximas consultas @@ -264,15 +260,7 @@ export default function DashboardPage() {
-
-
-
-

Relatórios Pendentes

-

{pendingReports.length}

-
- -
-
+
{/* 6. AÇÕES RÁPIDAS - Responsivo: stack em mobile, wrap em desktop */} @@ -294,11 +282,6 @@ export default function DashboardPage() { Novo Médico Médico -
@@ -330,28 +313,7 @@ export default function DashboardPage() { )}
- {/* 5. RELATÓRIOS PENDENTES */} -
-

- - Pendentes -

- {pendingReports.length > 0 ? ( -
- {pendingReports.map(report => ( -
-

{report.order_number}

-

{report.exam || 'Sem descrição'}

-
- ))} - -
- ) : ( -

Sem relatórios pendentes

- )} -
+
{/* 4. NOVOS USUÁRIOS */} From a0dfcd671c6969b58a1af7635946a9588212d1df Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Wed, 3 Dec 2025 23:04:37 -0300 Subject: [PATCH 31/37] fix: add hover calendar view options --- .../features/general/event-manager.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/susconecta/components/features/general/event-manager.tsx b/susconecta/components/features/general/event-manager.tsx index 9dd775c..fbc36f0 100644 --- a/susconecta/components/features/general/event-manager.tsx +++ b/susconecta/components/features/general/event-manager.tsx @@ -384,37 +384,37 @@ export function EventManager({ {/* Desktop: Button group */}
@@ -211,15 +253,17 @@ export default function RelatoriosPage() { {loading ? (
Carregando dados...
) : ( - - - - - - - - - +
+ + + + + + + + + +
)}
@@ -229,9 +273,10 @@ export default function RelatoriosPage() {

Pacientes Mais Atendidos

- +
- +
+
@@ -257,15 +302,17 @@ export default function RelatoriosPage() { )}
Paciente
+
{/* Médicos mais produtivos */}

Médicos Mais Produtivos

- +
- +
+
@@ -291,6 +338,7 @@ export default function RelatoriosPage() { )}
Médico
+
From b971cc38ba4672b0813715fd1ea82e52232279ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Wed, 3 Dec 2025 23:45:25 -0300 Subject: [PATCH 33/37] fix: doctor-exceptions --- susconecta/app/profissional/page.tsx | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx index ab84d41..431d463 100644 --- a/susconecta/app/profissional/page.tsx +++ b/susconecta/app/profissional/page.tsx @@ -2841,7 +2841,9 @@ const ProfissionalPage = () => { // Filtrar apenas a primeira exceção de cada data const exceptionByDate = new Map(); (exceptions || []).forEach((ex) => { - const date = String(ex.exception_date ?? ex.date ?? ''); + // Alguns backends/versões usam nomes diferentes para a data da exceção. + // Fazemos cast para any ao verificar campos legados para satisfazer o tipo DoctorException. + const date = String(((ex as any).exception_date) ?? ((ex as any).exceptionDate) ?? ex.date ?? ''); if (!exceptionByDate.has(date)) { exceptionByDate.set(date, ex); } @@ -2928,7 +2930,7 @@ const ProfissionalPage = () => { {/* Exceções */}

Exceções (Bloqueios/Liberações)

- {exceptLoading ? ( + {exceptLoading ? (
Carregando exceções…
) : filteredExceptions && filteredExceptions.length > 0 ? (
@@ -2938,15 +2940,26 @@ const ProfissionalPage = () => {
{(() => { try { - const [y, m, d] = String(ex.exception_date ?? ex.date).split('-'); - return `${d}/${m}/${y}`; + // Normaliza possíveis nomes de campo (exception_date, exceptionDate, date) e formata com fallback + const dateRaw = (ex as any).exception_date ?? (ex as any).exceptionDate ?? ex.date ?? ''; + const parts = String(dateRaw).split('-'); + if (parts.length >= 3) { + const [y, m, d] = parts; + return `${d}/${m}/${y}`; + } + // fallback: tentar parse ISO/locale + const dt = new Date(String(dateRaw)); + if (!isNaN(dt.getTime())) { + return `${String(dt.getDate()).padStart(2, '0')}/${String(dt.getMonth() + 1).padStart(2, '0')}/${dt.getFullYear()}`; + } + return String(dateRaw); } catch (e) { - return ex.exception_date ?? ex.date; + return ((ex as any).exception_date ?? (ex as any).exceptionDate ?? ex.date) as any; } })()}
- Tipo: {ex.kind || 'bloqueio'} • Motivo: {ex.reason || '—'} + Tipo: {(ex as any).kind || 'bloqueio'} • Motivo: {(ex as any).reason || '—'}
From 03285799f60a39c4271a5051a9967ca7e7cd53e1 Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Wed, 3 Dec 2025 23:48:47 -0300 Subject: [PATCH 34/37] fix(dashboard): expand upcoming appointments card to full width" --- susconecta/app/(main-routes)/dashboard/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/susconecta/app/(main-routes)/dashboard/page.tsx b/susconecta/app/(main-routes)/dashboard/page.tsx index 1f953f6..176da19 100644 --- a/susconecta/app/(main-routes)/dashboard/page.tsx +++ b/susconecta/app/(main-routes)/dashboard/page.tsx @@ -286,8 +286,8 @@ export default function DashboardPage() {
{/* 2. PRÓXIMAS CONSULTAS */} -
-
+
+

Próximas Consultas (7 dias)

{appointments.length > 0 ? (
From 3eb33e5eee5dbee4d84a744d67e2cc23bb8c324e Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Thu, 4 Dec 2025 00:13:00 -0300 Subject: [PATCH 35/37] fix(pacientes): Improved responsiveness to the 'Detalhes do Paciente' modal --- .../app/(main-routes)/pacientes/page.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/susconecta/app/(main-routes)/pacientes/page.tsx b/susconecta/app/(main-routes)/pacientes/page.tsx index aa149a4..0bb939c 100644 --- a/susconecta/app/(main-routes)/pacientes/page.tsx +++ b/susconecta/app/(main-routes)/pacientes/page.tsx @@ -539,27 +539,27 @@ export default function PacientesPage() {
-
- - {viewingPatient.full_name} +
+ + {viewingPatient.full_name}
-
- - {viewingPatient.cpf} +
+ + {viewingPatient.cpf}
-
- - {viewingPatient.phone_mobile} +
+ + {viewingPatient.phone_mobile}
-
- - +
+ + {`${viewingPatient.street || ''}, ${viewingPatient.number || ''} - ${viewingPatient.neighborhood || ''}, ${viewingPatient.city || ''} - ${viewingPatient.state || ''}`}
-
- - {viewingPatient.notes || "Nenhuma"} +
+ + {viewingPatient.notes || "Nenhuma"}
From a01c7cf2868f4803178d86be5541114fae0985bf Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Thu, 4 Dec 2025 00:16:09 -0300 Subject: [PATCH 36/37] =?UTF-8?q?fix(doutores):=20Improved=20responsivenes?= =?UTF-8?q?s=20to=20the=20'Detalhes=20do=20M=C3=A9dico'=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(main-routes)/doutores/page.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/susconecta/app/(main-routes)/doutores/page.tsx b/susconecta/app/(main-routes)/doutores/page.tsx index 72373f0..e2bc619 100644 --- a/susconecta/app/(main-routes)/doutores/page.tsx +++ b/susconecta/app/(main-routes)/doutores/page.tsx @@ -844,27 +844,27 @@ export default function DoutoresPage() {
-
- - {viewingDoctor?.full_name} +
+ + {viewingDoctor?.full_name}
-
- - +
+ + {viewingDoctor?.especialidade}
-
- - {viewingDoctor?.crm} +
+ + {viewingDoctor?.crm}
-
- - {viewingDoctor?.email} +
+ + {viewingDoctor?.email}
-
- - {viewingDoctor?.telefone} +
+ + {viewingDoctor?.telefone}
From 733a4188c10bd9dbe389e83177c2bf7cb63f7924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= Date: Thu, 4 Dec 2025 01:09:26 -0300 Subject: [PATCH 37/37] fix: appoiment-date --- .../features/forms/availability-form.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/susconecta/components/features/forms/availability-form.tsx b/susconecta/components/features/forms/availability-form.tsx index 2b6af56..e449b86 100644 --- a/susconecta/components/features/forms/availability-form.tsx +++ b/susconecta/components/features/forms/availability-form.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from 'react' +import { useState, useEffect, useMemo } from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogFooter, AlertDialogAction, AlertDialogCancel } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' @@ -46,12 +46,14 @@ export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved, }; // Get list of already used weekdays (excluding current one in edit mode) - const usedWeekdays = new Set( - (existingAvailabilities || []) - .filter(a => mode === 'edit' ? a.id !== availability?.id : true) - .map(a => normalizeWeekdayForComparison(a.weekday)) - .filter(Boolean) - ); + const usedWeekdays = useMemo(() => { + return new Set( + (existingAvailabilities || []) + .filter(a => mode === 'edit' ? a.id !== availability?.id : true) + .map(a => normalizeWeekdayForComparison(a.weekday)) + .filter(Boolean) + ); + }, [existingAvailabilities, mode, availability?.id]); // When editing, populate state from availability prop useEffect(() => {