From c4bf7b4aebe167dc998ea849e43e2524358125d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Deir=C3=B3=20Rodrigues?= Date: Fri, 7 Nov 2025 13:25:15 -0300 Subject: [PATCH 01/44] =?UTF-8?q?Informa=C3=A7=C3=B5es=20importantes=20con?= =?UTF-8?q?sulta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/patient/schedule/page.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/patient/schedule/page.tsx b/app/patient/schedule/page.tsx index 68c8fda..e8423a7 100644 --- a/app/patient/schedule/page.tsx +++ b/app/patient/schedule/page.tsx @@ -502,6 +502,20 @@ export default function ScheduleAppointment() { + {/* Card de Informações Importantes */} + + + Informações Importantes + + +
    +
  • Chegue com 15 minutos de antecedência
  • +
  • Traga documento com foto
  • +
  • Traga carteirinha do convênio
  • +
+
+
+
- + ); } \ No newline at end of file diff --git a/app/doctor/dashboard/page.tsx b/app/doctor/dashboard/page.tsx index cf9bad5..fd88869 100644 --- a/app/doctor/dashboard/page.tsx +++ b/app/doctor/dashboard/page.tsx @@ -14,6 +14,7 @@ import { AvailabilityService } from "@/services/availabilityApi.mjs"; import { exceptionsService } from "@/services/exceptionApi.mjs"; import { doctorsService } from "@/services/doctorsApi.mjs"; import { usersService } from "@/services/usersApi.mjs"; +import Sidebar from "@/components/Sidebar"; type Availability = { id: string; @@ -231,7 +232,7 @@ export default function PatientDashboard() { }, [availability]); return ( - +

Dashboard

@@ -409,6 +410,6 @@ export default function PatientDashboard() {
- + ); } diff --git a/app/doctor/disponibilidade/excecoes/page.tsx b/app/doctor/disponibilidade/excecoes/page.tsx index 3e7b316..932cb3f 100644 --- a/app/doctor/disponibilidade/excecoes/page.tsx +++ b/app/doctor/disponibilidade/excecoes/page.tsx @@ -3,14 +3,12 @@ import type React from "react"; import Link from "next/link"; import { useState, useEffect } from "react"; -import DoctorLayout from "@/components/doctor-layout"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Clock, Calendar as CalendarIcon, MapPin, Phone, User, X, RefreshCw } from "lucide-react"; -import { Badge } from "@/components/ui/badge"; +import { Calendar as CalendarIcon, RefreshCw } from "lucide-react"; import { useRouter } from "next/navigation"; import { toast } from "@/hooks/use-toast"; import { exceptionsService } from "@/services/exceptionApi.mjs"; @@ -19,6 +17,7 @@ import { exceptionsService } from "@/services/exceptionApi.mjs"; import { Calendar } from "@/components/ui/calendar"; import { format } from "date-fns"; // Usaremos o date-fns para formatação e comparação de datas import { doctorsService } from "@/services/doctorsApi.mjs"; +import Sidebar from "@/components/Sidebar"; type Doctor = { id: string; @@ -147,7 +146,7 @@ export default function ExceptionPage() { const displayDate = selectedCalendarDate ? new Date(selectedCalendarDate).toLocaleDateString("pt-BR", { weekday: "long", day: "2-digit", month: "long" }) : "Selecione uma data"; return ( - +

Adicione exceções

@@ -254,6 +253,6 @@ export default function ExceptionPage() {
-
+ ); } diff --git a/app/doctor/disponibilidade/page.tsx b/app/doctor/disponibilidade/page.tsx index 36f0e98..ff0ce77 100644 --- a/app/doctor/disponibilidade/page.tsx +++ b/app/doctor/disponibilidade/page.tsx @@ -7,7 +7,6 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import DoctorLayout from "@/components/doctor-layout"; import { AvailabilityService } from "@/services/availabilityApi.mjs"; import { usersService } from "@/services/usersApi.mjs"; @@ -17,9 +16,10 @@ import { toast } from "@/hooks/use-toast"; import { useRouter } from "next/navigation"; import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; -import { Eye, Edit, Calendar, Trash2 } from "lucide-react"; +import { Edit, Trash2 } from "lucide-react"; import { AvailabilityEditModal } from "@/components/ui/availability-edit-modal"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; +import Sidebar from "@/components/Sidebar"; // ... (Interfaces de tipo omitidas para brevidade, pois não foram alteradas) @@ -323,7 +323,7 @@ export default function AvailabilityPage() { }; return ( - +
@@ -506,6 +506,6 @@ export default function AvailabilityPage() { onSubmit={handleEdit} /> - + ); } \ No newline at end of file diff --git a/app/doctor/medicos/[id]/editar/page.tsx b/app/doctor/medicos/[id]/editar/page.tsx index 0049db2..7bc5595 100644 --- a/app/doctor/medicos/[id]/editar/page.tsx +++ b/app/doctor/medicos/[id]/editar/page.tsx @@ -12,7 +12,7 @@ import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { ArrowLeft, Save } from "lucide-react"; import Link from "next/link"; -import DoctorLayout from "@/components/doctor-layout"; +import Sidebar from "@/components/Sidebar"; // Mock data - in a real app, this would come from an API const mockDoctors = [ @@ -124,7 +124,7 @@ export default function EditarMedicoPage() { }; return ( - +
@@ -512,6 +512,6 @@ export default function EditarMedicoPage() {
-
+ ); } diff --git a/app/doctor/medicos/[id]/laudos/[laudoId]/editar/page.tsx b/app/doctor/medicos/[id]/laudos/[laudoId]/editar/page.tsx index 02f1e6c..52525fd 100644 --- a/app/doctor/medicos/[id]/laudos/[laudoId]/editar/page.tsx +++ b/app/doctor/medicos/[id]/laudos/[laudoId]/editar/page.tsx @@ -2,7 +2,6 @@ import { useParams, useRouter } from "next/navigation"; import { useState, useEffect } from "react"; -import DoctorLayout from "@/components/doctor-layout"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; @@ -17,6 +16,7 @@ import { format } from "date-fns"; import TiptapEditor from "@/components/ui/tiptap-editor"; import { Skeleton } from "@/components/ui/skeleton"; import { reportsApi } from "@/services/reportsApi.mjs"; +import Sidebar from "@/components/Sidebar"; export default function EditarLaudoPage() { const router = useRouter(); @@ -108,7 +108,7 @@ export default function EditarLaudoPage() { if (loading) { return ( - +
@@ -130,12 +130,12 @@ export default function EditarLaudoPage() {
-
+ ) } return ( - +
@@ -232,6 +232,6 @@ export default function EditarLaudoPage() {
-
+ ); } \ No newline at end of file diff --git a/app/doctor/medicos/[id]/laudos/novo/page.tsx b/app/doctor/medicos/[id]/laudos/novo/page.tsx index 215d3a6..a570e86 100644 --- a/app/doctor/medicos/[id]/laudos/novo/page.tsx +++ b/app/doctor/medicos/[id]/laudos/novo/page.tsx @@ -18,6 +18,7 @@ import TiptapEditor from "@/components/ui/tiptap-editor"; import { reportsApi } from "@/services/reportsApi.mjs"; import DoctorLayout from "@/components/doctor-layout"; +import Sidebar from "@/components/Sidebar"; @@ -97,7 +98,7 @@ export default function NovoLaudoPage() { }; return ( - +
@@ -189,6 +190,6 @@ export default function NovoLaudoPage() {
-
+ ); } \ No newline at end of file diff --git a/app/doctor/medicos/[id]/laudos/page.tsx b/app/doctor/medicos/[id]/laudos/page.tsx index 981b250..848bb40 100644 --- a/app/doctor/medicos/[id]/laudos/page.tsx +++ b/app/doctor/medicos/[id]/laudos/page.tsx @@ -8,7 +8,7 @@ import Link from 'next/link'; import { useParams } from 'next/navigation'; import { api } from '@/services/api.mjs'; import { reportsApi } from '@/services/reportsApi.mjs'; -import DoctorLayout from '@/components/doctor-layout'; +import Sidebar from '@/components/Sidebar'; export default function LaudosPage() { const [patient, setPatient] = useState(null); @@ -49,7 +49,7 @@ export default function LaudosPage() { const paginate = (pageNumber) => setCurrentPage(pageNumber); return ( - +
{loading ? (

Carregando...

@@ -123,6 +123,6 @@ export default function LaudosPage() { )}
-
+ ); } \ No newline at end of file diff --git a/app/doctor/medicos/novo/page.tsx b/app/doctor/medicos/novo/page.tsx index dee0314..8aa1b75 100644 --- a/app/doctor/medicos/novo/page.tsx +++ b/app/doctor/medicos/novo/page.tsx @@ -9,7 +9,7 @@ import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Upload, Plus, X, ChevronDown } from "lucide-react"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; -import DoctorLayout from "@/components/doctor-layout"; +import Sidebar from "@/components/Sidebar"; export default function NovoMedicoPage() { const [anexosOpen, setAnexosOpen] = useState(false); @@ -24,7 +24,7 @@ export default function NovoMedicoPage() { }; return ( - +
@@ -466,6 +466,6 @@ export default function NovoMedicoPage() {
- + ); } diff --git a/app/doctor/medicos/page.tsx b/app/doctor/medicos/page.tsx index bc18221..cd357a1 100644 --- a/app/doctor/medicos/page.tsx +++ b/app/doctor/medicos/page.tsx @@ -2,25 +2,14 @@ "use client"; import { useEffect, useState, useCallback } from "react"; -import DoctorLayout from "@/components/doctor-layout"; import Link from "next/link"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { Eye, Edit, Calendar, Trash2, Loader2 } from "lucide-react"; import { api } from "@/services/api.mjs"; import { PatientDetailsModal } from "@/components/ui/patient-details-modal"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; +import Sidebar from "@/components/Sidebar"; interface Paciente { id: string; @@ -171,7 +160,7 @@ export default function PacientesPage() { }, [fetchPacientes]); return ( - +
{/* Cabeçalho */}
@@ -363,6 +352,6 @@ export default function PacientesPage() { isOpen={isModalOpen} onClose={handleCloseModal} /> - + ); } \ No newline at end of file diff --git a/app/finance/home/page.tsx b/app/finance/home/page.tsx index c9e0567..1c07932 100644 --- a/app/finance/home/page.tsx +++ b/app/finance/home/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; -import FinancierLayout from "@/components/finance-layout"; +import Sidebar from "@/components/Sidebar"; interface Paciente { id: string; @@ -14,43 +14,10 @@ interface Paciente { } export default function PacientesPage() { - const [pacientes, setPacientes] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - async function fetchPacientes() { - try { - setLoading(true); - setError(null); - const res = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes"); - if (!res.ok) throw new Error(`HTTP ${res.status}`); - const json = await res.json(); - const items = Array.isArray(json?.data) ? json.data : []; - - const mapped = items.map((p: any) => ({ - id: String(p.id ?? ""), - nome: p.nome ?? "", - telefone: p?.contato?.celular ?? p?.contato?.telefone1 ?? p?.telefone ?? "", - cidade: p?.endereco?.cidade ?? p?.cidade ?? "", - estado: p?.endereco?.estado ?? p?.estado ?? "", - ultimoAtendimento: p.ultimo_atendimento ?? p.ultimoAtendimento ?? "", - proximoAtendimento: p.proximo_atendimento ?? p.proximoAtendimento ?? "", - })); - - setPacientes(mapped); - } catch (e: any) { - setError(e?.message || "Erro ao carregar pacientes"); - } finally { - setLoading(false); - } - } - fetchPacientes(); - }, []); return ( - +
-
+ ); } diff --git a/app/manager/dashboard/page.tsx b/app/manager/dashboard/page.tsx index df56541..7359967 100644 --- a/app/manager/dashboard/page.tsx +++ b/app/manager/dashboard/page.tsx @@ -8,6 +8,7 @@ import Link from "next/link"; import React, { useState, useEffect } from "react"; import { usersService } from "services/usersApi.mjs"; import { doctorsService } from "services/doctorsApi.mjs"; +import Sidebar from "@/components/Sidebar"; export default function ManagerDashboard() { // 🔹 Estados para usuários @@ -55,7 +56,7 @@ export default function ManagerDashboard() { }, []); return ( - +
{/* Cabeçalho */}
@@ -185,6 +186,6 @@ export default function ManagerDashboard() {
-
+ ); } diff --git a/app/manager/home/page.tsx b/app/manager/home/page.tsx index 03b64a5..0d35193 100644 --- a/app/manager/home/page.tsx +++ b/app/manager/home/page.tsx @@ -1,25 +1,16 @@ "use client"; import React, { useEffect, useState, useCallback, useMemo } from "react" -import ManagerLayout from "@/components/manager-layout"; import Link from "next/link" import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react" -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog" +import { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react" +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog" import { doctorsService } from "services/doctorsApi.mjs"; +import Sidebar from "@/components/Sidebar"; interface Doctor { @@ -193,7 +184,7 @@ export default function DoctorsPage() { return ( - +
{/* Cabeçalho */} @@ -430,6 +421,6 @@ export default function DoctorsPage() {
-
+ ); } \ No newline at end of file diff --git a/app/manager/pacientes/[id]/editar/page.tsx b/app/manager/pacientes/[id]/editar/page.tsx index 254be97..51858d7 100644 --- a/app/manager/pacientes/[id]/editar/page.tsx +++ b/app/manager/pacientes/[id]/editar/page.tsx @@ -13,9 +13,8 @@ import { Checkbox } from "@/components/ui/checkbox"; import { ArrowLeft, Save, Trash2, Paperclip, Upload } from "lucide-react"; import Link from "next/link"; import { useToast } from "@/hooks/use-toast"; -import SecretaryLayout from "@/components/secretary-layout"; import { patientsService } from "@/services/patientsApi.mjs"; -import { json } from "stream/consumers"; +import Sidebar from "@/components/Sidebar"; export default function EditarPacientePage() { const router = useRouter(); @@ -247,7 +246,7 @@ export default function EditarPacientePage() { }; return ( - +
@@ -677,6 +676,6 @@ export default function EditarPacientePage() {
-
+ ); } diff --git a/app/manager/pacientes/page.tsx b/app/manager/pacientes/page.tsx index 5fb159b..e10990d 100644 --- a/app/manager/pacientes/page.tsx +++ b/app/manager/pacientes/page.tsx @@ -6,10 +6,10 @@ import Link from "next/link"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react"; +import { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { patientsService } from "@/services/patientsApi.mjs"; -import ManagerLayout from "@/components/manager-layout"; +import Sidebar from "@/components/Sidebar"; // Defina o tamanho da página. const PAGE_SIZE = 5; @@ -145,7 +145,7 @@ export default function PacientesPage() { }; return ( - +
{/* Header (Responsividade OK) */}
@@ -449,6 +449,6 @@ export default function PacientesPage() {
- + ); } \ No newline at end of file diff --git a/app/manager/usuario/[id]/editar/page.tsx b/app/manager/usuario/[id]/editar/page.tsx index 50cb953..030891d 100644 --- a/app/manager/usuario/[id]/editar/page.tsx +++ b/app/manager/usuario/[id]/editar/page.tsx @@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Save, Loader2, ArrowLeft } from "lucide-react" -import ManagerLayout from "@/components/manager-layout" +import Sidebar from "@/components/Sidebar" // Mock user service for demonstration. Replace with your actual API service. const usersService = { @@ -155,17 +155,17 @@ export default function EditarUsuarioPage() { if (loading) { return ( - +

Carregando dados do usuário...

-
+ ); } return ( - +
@@ -274,6 +274,6 @@ export default function EditarUsuarioPage() {
- + ); } \ No newline at end of file diff --git a/app/manager/usuario/novo/page.tsx b/app/manager/usuario/novo/page.tsx index 1e63d72..318669a 100644 --- a/app/manager/usuario/novo/page.tsx +++ b/app/manager/usuario/novo/page.tsx @@ -10,10 +10,9 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Save, Loader2, Pause } from "lucide-react"; -import ManagerLayout from "@/components/manager-layout"; import { usersService } from "@/services/usersApi.mjs"; -import { doctorsService } from "@/services/doctorsApi.mjs"; // Importação adicionada -import { login } from "services/api.mjs"; +import { doctorsService } from "@/services/doctorsApi.mjs"; +import Sidebar from "@/components/Sidebar"; interface UserFormData { email: string; @@ -145,7 +144,7 @@ export default function NovoUsuarioPage() { const isMedico = formData.papel === "medico"; return ( - +
@@ -250,6 +249,6 @@ export default function NovoUsuarioPage() {
- + ); } diff --git a/app/manager/usuario/page.tsx b/app/manager/usuario/page.tsx index 805fb0c..634bf3d 100644 --- a/app/manager/usuario/page.tsx +++ b/app/manager/usuario/page.tsx @@ -2,28 +2,14 @@ "use client"; import React, { useEffect, useState, useCallback } from "react"; -import ManagerLayout from "@/components/manager-layout"; import Link from "next/link"; import { Button } from "@/components/ui/button"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Plus, Eye, Filter, Loader2 } from "lucide-react"; -import { - AlertDialog, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog"; +import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { api, login } from "services/api.mjs"; import { usersService } from "services/usersApi.mjs"; +import Sidebar from "@/components/Sidebar"; interface FlatUser { id: string; @@ -192,7 +178,7 @@ export default function UsersPage() { return ( - +
{/* Header */} @@ -424,6 +410,6 @@ export default function UsersPage() {
-
+ ); } \ No newline at end of file diff --git a/app/patient/appointments/page.tsx b/app/patient/appointments/page.tsx index 3e271b6..23f8ee6 100644 --- a/app/patient/appointments/page.tsx +++ b/app/patient/appointments/page.tsx @@ -1,7 +1,6 @@ "use client"; import { useState, useEffect } from "react"; -import PatientLayout from "@/components/patient-layout"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; @@ -10,6 +9,7 @@ import { toast } from "sonner"; import { appointmentsService } from "@/services/appointmentsApi.mjs"; import { usersService } from "@/services/usersApi.mjs"; +import Sidebar from "@/components/Sidebar"; // Tipagem correta para o usuário interface UserProfile { @@ -129,7 +129,7 @@ export default function PatientAppointmentsPage() { }; return ( - +
@@ -185,6 +185,6 @@ export default function PatientAppointmentsPage() { )}
- + ); } diff --git a/app/patient/dashboard/page.tsx b/app/patient/dashboard/page.tsx index c829041..31875f8 100644 --- a/app/patient/dashboard/page.tsx +++ b/app/patient/dashboard/page.tsx @@ -3,10 +3,11 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import { Button } from "@/components/ui/button" import { Calendar, Clock, User, Plus } from "lucide-react" import Link from "next/link" +import Sidebar from "@/components/Sidebar" export default function PatientDashboard() { return ( - +

Dashboard

@@ -108,6 +109,6 @@ export default function PatientDashboard() {
-
+ ) } diff --git a/app/patient/profile/page.tsx b/app/patient/profile/page.tsx index 0d92b0c..7cf3544 100644 --- a/app/patient/profile/page.tsx +++ b/app/patient/profile/page.tsx @@ -8,6 +8,7 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Textarea } from "@/components/ui/textarea" import { User, Mail, Phone, Calendar, FileText } from "lucide-react" +import Sidebar from "@/components/Sidebar" interface PatientData { name: string @@ -50,7 +51,7 @@ export default function PatientProfile() { } return ( - +
@@ -217,6 +218,6 @@ export default function PatientProfile() {
-
+ ) } diff --git a/app/patient/reports/page.tsx b/app/patient/reports/page.tsx index dc12193..d1fa91c 100644 --- a/app/patient/reports/page.tsx +++ b/app/patient/reports/page.tsx @@ -1,13 +1,13 @@ "use client" import { useState, useEffect } from "react" -import PatientLayout from "@/components/patient-layout" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { toast } from "@/hooks/use-toast" import { FileText, Download, Eye, Calendar, User, X } from "lucide-react" +import Sidebar from "@/components/Sidebar" interface Report { id: string @@ -287,7 +287,7 @@ export default function ReportsPage() { const pendingReports = reports.filter((report) => report.status === "pendente") return ( - +

Meus Laudos

@@ -536,6 +536,6 @@ export default function ReportsPage() {
- + ) } diff --git a/app/patient/schedule/page.tsx b/app/patient/schedule/page.tsx index 68c8fda..cea4a6e 100644 --- a/app/patient/schedule/page.tsx +++ b/app/patient/schedule/page.tsx @@ -8,8 +8,7 @@ import { Calendar as CalendarShadcn } from "@/components/ui/calendar"; import { format, addDays } from "date-fns"; import { useState, useEffect, useCallback, useRef } from "react"; -import { Calendar, Clock, User, StickyNote } from "lucide-react"; -import PatientLayout from "@/components/patient-layout"; +import { Calendar, User, StickyNote } from "lucide-react"; import { Card, CardContent, @@ -27,6 +26,7 @@ import { } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "@/hooks/use-toast"; +import Sidebar from "@/components/Sidebar"; interface Doctor { @@ -363,7 +363,7 @@ export default function ScheduleAppointment() { return ( - +

Agendar Consulta

@@ -550,6 +550,6 @@ export default function ScheduleAppointment() {
)}
-
+ ); } diff --git a/app/secretary/appointments/page.tsx b/app/secretary/appointments/page.tsx index 679ba8e..9dbb676 100644 --- a/app/secretary/appointments/page.tsx +++ b/app/secretary/appointments/page.tsx @@ -1,20 +1,17 @@ "use client"; import { useState, useEffect } from "react"; -import SecretaryLayout from "@/components/secretary-layout"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Dialog } from "@/components/ui/dialog"; import { Calendar, Clock, MapPin, Phone, User, Trash2, Pencil } from "lucide-react"; import { toast } from "sonner"; import Link from "next/link"; import { appointmentsService } from "@/services/appointmentsApi.mjs"; import { patientsService } from "@/services/patientsApi.mjs"; import { doctorsService } from "@/services/doctorsApi.mjs"; +import Sidebar from "@/components/Sidebar"; export default function SecretaryAppointments() { const [appointments, setAppointments] = useState([]); @@ -144,7 +141,7 @@ export default function SecretaryAppointments() { const appointmentStatuses = ["requested", "confirmed", "checked_in", "completed", "cancelled", "no_show"]; return ( - +
@@ -225,6 +222,6 @@ export default function SecretaryAppointments() { {/* ... (código do modal de deleção) ... */} - + ); } \ No newline at end of file diff --git a/app/secretary/dashboard/page.tsx b/app/secretary/dashboard/page.tsx index e37141c..ff47223 100644 --- a/app/secretary/dashboard/page.tsx +++ b/app/secretary/dashboard/page.tsx @@ -1,10 +1,6 @@ "use client"; -import SecretaryLayout from "@/components/secretary-layout"; -import { - Card, - CardContent, - CardDescription, +import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; @@ -14,6 +10,7 @@ import Link from "next/link"; import React, { useState, useEffect } from "react"; import { patientsService } from "@/services/patientsApi.mjs"; import { appointmentsService } from "@/services/appointmentsApi.mjs"; +import Sidebar from "@/components/Sidebar"; export default function SecretaryDashboard() { // Estados @@ -100,7 +97,7 @@ export default function SecretaryDashboard() { }, []); return ( - +
{/* Cabeçalho */}
@@ -299,6 +296,6 @@ export default function SecretaryDashboard() {
-
+ ); } diff --git a/app/secretary/pacientes/[id]/editar/page.tsx b/app/secretary/pacientes/[id]/editar/page.tsx index 00f11fe..fa7e5d9 100644 --- a/app/secretary/pacientes/[id]/editar/page.tsx +++ b/app/secretary/pacientes/[id]/editar/page.tsx @@ -13,9 +13,8 @@ import { Checkbox } from "@/components/ui/checkbox"; import { ArrowLeft, Save, Trash2, Paperclip, Upload } from "lucide-react"; import Link from "next/link"; import { useToast } from "@/hooks/use-toast"; -import SecretaryLayout from "@/components/secretary-layout"; import { patientsService } from "@/services/patientsApi.mjs"; -import { json } from "stream/consumers"; +import Sidebar from "@/components/Sidebar"; export default function EditarPacientePage() { const router = useRouter(); @@ -247,7 +246,7 @@ export default function EditarPacientePage() { }; return ( - +
@@ -677,6 +676,6 @@ export default function EditarPacientePage() {
-
+ ); } diff --git a/app/secretary/pacientes/novo/page.tsx b/app/secretary/pacientes/novo/page.tsx index a028ea6..e7e94cd 100644 --- a/app/secretary/pacientes/novo/page.tsx +++ b/app/secretary/pacientes/novo/page.tsx @@ -1,4 +1,3 @@ -// Caminho: app/(manager)/usuario/novo/page.tsx "use client"; import { useState } from "react"; @@ -7,13 +6,9 @@ import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -// O Select foi removido pois não é mais necessário import { Save, Loader2 } from "lucide-react"; -import ManagerLayout from "@/components/manager-layout"; -// Os imports originais foram mantidos, como solicitado import { usersService } from "services/usersApi.mjs"; -import { doctorsService } from "services/doctorsApi.mjs"; -import { login } from "services/api.mjs"; +import Sidebar from "@/components/Sidebar"; // Interface simplificada para refletir apenas os campos necessários interface UserFormData { @@ -97,7 +92,7 @@ export default function NovoUsuarioPage() { }; return ( - +
@@ -167,6 +162,6 @@ export default function NovoUsuarioPage() {
- + ); } \ No newline at end of file diff --git a/app/secretary/pacientes/page.tsx b/app/secretary/pacientes/page.tsx index 623a966..bec32d8 100644 --- a/app/secretary/pacientes/page.tsx +++ b/app/secretary/pacientes/page.tsx @@ -8,8 +8,8 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; -import SecretaryLayout from "@/components/secretary-layout"; import { patientsService } from "@/services/patientsApi.mjs"; +import Sidebar from "@/components/Sidebar"; // Defina o tamanho da página. const PAGE_SIZE = 5; @@ -145,7 +145,7 @@ export default function PacientesPage() { }; return ( - +
{/* Header (Responsividade OK) */}
@@ -457,6 +457,6 @@ export default function PacientesPage() {
- + ); } \ No newline at end of file diff --git a/app/secretary/schedule/page.tsx b/app/secretary/schedule/page.tsx index 7eeb2c3..e6b913d 100644 --- a/app/secretary/schedule/page.tsx +++ b/app/secretary/schedule/page.tsx @@ -2,7 +2,6 @@ import type React from "react"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; -import SecretaryLayout from "@/components/secretary-layout"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -15,6 +14,7 @@ import { doctorsService } from "@/services/doctorsApi.mjs"; import { appointmentsService } from "@/services/appointmentsApi.mjs"; import { usersService } from "@/services/usersApi.mjs"; import { toast } from "sonner"; // Para notificações +import Sidebar from "@/components/Sidebar"; export default function ScheduleAppointment() { const router = useRouter(); @@ -153,7 +153,7 @@ export default function ScheduleAppointment() { } }; return ( - +

Agendar Consulta

@@ -350,6 +350,6 @@ export default function ScheduleAppointment() {
-
+ ); } \ No newline at end of file diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx new file mode 100644 index 0000000..01640e4 --- /dev/null +++ b/components/Sidebar.tsx @@ -0,0 +1,320 @@ +// Caminho: [seu-caminho]/ManagerLayout.tsx +"use client"; + +import type React from "react"; +import { useState, useEffect } from "react"; +import { useRouter, usePathname } from "next/navigation"; +import Link from "next/link"; +import Cookies from "js-cookie"; // Mantido apenas para a limpeza de segurança no logout +import { api } from "@/services/api.mjs"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Search, Bell, Calendar, User, LogOut, ChevronLeft, ChevronRight, Home } from "lucide-react"; +import SidebarUserSection from "@/components/ui/userToolTip"; + +interface UserData { + id: string; + email: string; + app_metadata: { + user_role: string; + }; + user_metadata: { + cpf: string; + email_verified: boolean; + full_name: string; + phone_mobile: string; + role: string; + }; + identities: { + identity_id: string; + id: string; + user_id: string; + provider: string; + }[]; + is_anonymous: boolean; +} + + +interface MenuItem { + href: string; + icon: React.ElementType; + label: string; +} + +type Role = "manager" | "doctor" | "secretary" | "patient" | "admin"; + +interface SidebarProps { + children: React.ReactNode; +} + +export default function Sidebar({ children }: SidebarProps) { + const [userData, setUserData] = useState(); + const [role, setRole] = useState(); + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); + const [showLogoutDialog, setShowLogoutDialog] = useState(false); + const router = useRouter(); + const pathname = usePathname(); + + useEffect(() => { + const userInfoString = localStorage.getItem("user_info"); + // --- ALTERAÇÃO 1: Buscando o token no localStorage --- + const token = localStorage.getItem("token"); + + if (userInfoString && token) { + const userInfo = JSON.parse(userInfoString); + + setUserData({ + id: userInfo.id ?? "", + email: userInfo.email ?? "", + app_metadata: { + user_role: userInfo.app_metadata?.user_role ?? "patient", + }, + user_metadata: { + cpf: userInfo.user_metadata?.cpf ?? "", + email_verified: userInfo.user_metadata?.email_verified ?? false, + full_name: userInfo.user_metadata?.full_name ?? "", + phone_mobile: userInfo.user_metadata?.phone_mobile ?? "", + role: userInfo.user_metadata?.role ?? "", + }, + identities: + userInfo.identities?.map((identity: any) => ({ + identity_id: identity.identity_id ?? "", + id: identity.id ?? "", + user_id: identity.user_id ?? "", + provider: identity.provider ?? "", + })) ?? [], + is_anonymous: userInfo.is_anonymous ?? false, + }); + setRole(userInfo.user_metadata?.role) + } else { + // O redirecionamento para /login já estava correto. Ótimo! + router.push("/login"); + } + }, [router]); + + useEffect(() => { + + + const handleResize = () => { + if (window.innerWidth < 1024) { + setSidebarCollapsed(true); + } else { + setSidebarCollapsed(false); + } + }; + handleResize(); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + const handleLogout = () => setShowLogoutDialog(true); + + // --- ALTERAÇÃO 2: A função de logout agora é MUITO mais simples --- + const confirmLogout = async () => { + try { + // Chama a função centralizada para fazer o logout no servidor + await api.logout(); + } catch (error) { + // O erro já é logado dentro da função api.logout, não precisamos fazer nada aqui + } finally { + // A responsabilidade do componente é apenas limpar o estado local e redirecionar + localStorage.removeItem("user_info"); + localStorage.removeItem("token"); + Cookies.remove("access_token"); // Limpeza de segurança + + setShowLogoutDialog(false); + router.push("/"); // Redireciona para a home + } + }; + + const cancelLogout = () => setShowLogoutDialog(false); + + const SetMenuItems = (role: any) => { + const patientItems: MenuItem[] = [ + { href: "/patient/dashboard", icon: Home, label: "Dashboard" }, + { href: "/patient/schedule", icon: Home, label: "Agendar Consulta" }, + { href: "/patient/appointments", icon: Home, label: "Minhas Consultas" }, + { href: "/patient/reports", icon: Home, label: "Meus Laudos" }, + { href: "/patient/profile", icon: Home, label: "Meus Dados" }, + ] + + const doctorItems: MenuItem[] = [ + { href: "/doctor/dashboard", icon: Home, label: "Dashboard" }, + { href: "/doctor/medicos", icon: User, label: "Gestão de Pacientes" }, + { href: "/doctor/consultas", icon: Calendar, label: "Consultas" }, + { href: "/doctor/disponibilidade", icon: User, label: "Disponibilidade" }, + ] + + const secretaryItems: MenuItem[] = [ + { href: "/secretary/dashboard", icon: Home, label: "Dashboard" }, + { href: "/secretary/appointments", icon: Calendar, label: "Consultas" }, + { href: "/secretary/schedule", icon: Calendar, label: "Agendar Consulta" }, + { href: "/secretary/pacientes", icon: User, label: "Gestão de Pacientes" }, + ] + + const managerItems: MenuItem[] = [ + { href: "/manager/dashboard", icon: Home, label: "Dashboard" }, + { href: "#", icon: Calendar, label: "Relatórios gerenciais" }, + { href: "/manager/usuario", icon: User, label: "Gestão de Usuários" }, + { href: "/manager/home", icon: User, label: "Gestão de Médicos" }, + { href: "/manager/pacientes", icon: User, label: "Gestão de Pacientes" }, + { href: "/doctor/consultas", icon: Calendar, label: "Consultas" }, //adicionar botão de voltar pra pagina anterior + ] + + let menuItems: MenuItem[]; + switch (role) { + case "manager": + menuItems = managerItems; + break; + case "admin": + menuItems = managerItems; + break; + case "doctor": + menuItems = doctorItems; + break; + case "secretary": + menuItems = secretaryItems; + break; + case "patient": + menuItems = patientItems; + break; + default: + menuItems = patientItems; + break; + } + return menuItems; + } + + const menuItems = SetMenuItems(role) + + + /* + const menuItems = [ + { href: "/manager/dashboard", icon: Home, label: "Dashboard" }, + { href: "#", icon: Calendar, label: "Relatórios gerenciais" }, + { href: "/manager/usuario", icon: User, label: "Gestão de Usuários" }, + { href: "/manager/home", icon: User, label: "Gestão de Médicos" }, + { href: "/manager/pacientes", icon: User, label: "Gestão de Pacientes" }, + { href: "#", icon: User, label: "Disponibilidade" }, + { href: "#", icon: Calendar, label: "Consultas" }, + { href: "#", icon: Calendar, label: "Agendar Consulta S" }, + { href: "#", icon: Home, label: "Agendar Consulta P" }, + { href: "#", icon: Home, label: "Minhas Consultas" }, + { href: "#", icon: Home, label: "Meus Laudos" }, + { href: "#", icon: Home, label: "Meus Dados" }, + ]; */ + + if (!userData) { + return ( +
+ Carregando... +
+ ); + } + + return ( +
+
+
+ {!sidebarCollapsed && ( +
+
+
+
+ MedConnect +
+ )} + +
+ + + + +
+ +
+
+
+
+ {/* tira essas divs e muda o header para bg-gray-50 */} +
+
+
{children}
+
+ + + + + Confirmar Saída + + Deseja realmente sair do sistema? Você precisará fazer login + novamente para acessar sua conta. + + + + + + + + +
+ ); +} diff --git a/components/ui/userToolTip.tsx b/components/ui/userToolTip.tsx new file mode 100644 index 0000000..fcf4512 --- /dev/null +++ b/components/ui/userToolTip.tsx @@ -0,0 +1,125 @@ +"use client"; + +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; +import { Button } from "@/components/ui/button"; +import { Home, LogOut } from "lucide-react"; +import { + Popover, + PopoverTrigger, + PopoverContent, +} from "@/components/ui/popover"; +import { usePathname } from "next/navigation"; +import Link from "next/link"; + +interface UserData { + user_metadata: { + full_name: string; + }; + app_metadata: { + user_role: string; + }; + email: string; +} + +interface Props { + userData: UserData; + sidebarCollapsed: boolean; + handleLogout: () => void; + isActive: boolean; +} + +export default function SidebarUserSection({ + userData, + sidebarCollapsed, + handleLogout, + isActive, +}: Props) { + const pathname = usePathname(); + const menuItems: any[] = [ + { href: "/patient/schedule", icon: Home, label: "Agendar Consulta" }, + { href: "/patient/appointments", icon: Home, label: "Minhas Consultas" }, + { href: "/patient/reports", icon: Home, label: "Meus Laudos" }, + { href: "/patient/profile", icon: Home, label: "Meus Dados" }, + ] + return ( +
+ {/* POPUP DE INFORMAÇÕES DO USUÁRIO */} + + +
+ + + + {userData.user_metadata.full_name + .split(" ") + .map((n) => n[0]) + .join("")} + + + + {!sidebarCollapsed && ( +
+

+ {userData.user_metadata.full_name} +

+

+ {userData.app_metadata.user_role} +

+
+ )} +
+
+ + {/* Card flutuante */} + + + +
+ + {/* Botão de sair */} + +
+ ); +} From 4376cdefd1d4190c1fd957260406134e72526c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Deir=C3=B3=20Rodrigues?= Date: Sat, 8 Nov 2025 18:36:17 -0300 Subject: [PATCH 03/44] =?UTF-8?q?Calend=C3=A1rio=20p=C3=A1gina=20de=20marc?= =?UTF-8?q?ar=20consultas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/patient/schedule/page.tsx | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/app/patient/schedule/page.tsx b/app/patient/schedule/page.tsx index e8423a7..f0fc0cc 100644 --- a/app/patient/schedule/page.tsx +++ b/app/patient/schedule/page.tsx @@ -10,21 +10,10 @@ import { format, addDays } from "date-fns"; import { useState, useEffect, useCallback, useRef } from "react"; import { Calendar, Clock, User, StickyNote } from "lucide-react"; import PatientLayout from "@/components/patient-layout"; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "@/components/ui/card"; +import {Card,CardContent,CardHeader,CardTitle,} from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import {Select,SelectContent,SelectItem,SelectTrigger,SelectValue,} from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "@/hooks/use-toast"; @@ -109,7 +98,6 @@ export default function ScheduleAppointment() { } }, []); - // --- Compute availability counts for next 90 days (efficient) --- const computeAvailabilityCountsPreview = async (doctorId: string, dispList: Disponibilidade[]) => { try { const today = new Date(); From a52f10d3624977081844b71c50d9d675caac3478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Deir=C3=B3=20Rodrigues?= Date: Sat, 8 Nov 2025 22:44:03 -0300 Subject: [PATCH 04/44] =?UTF-8?q?Cria=C3=A7=C3=A3o=20do=20componente=20de?= =?UTF-8?q?=20agendamento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/patient/schedule/page.tsx | 553 +------------------------- app/secretary/schedule/page.tsx | 361 +---------------- components/schedule/schedule-form.tsx | 477 ++++++++++++++++++++++ 3 files changed, 490 insertions(+), 901 deletions(-) create mode 100644 components/schedule/schedule-form.tsx diff --git a/app/patient/schedule/page.tsx b/app/patient/schedule/page.tsx index f0fc0cc..24c3f00 100644 --- a/app/patient/schedule/page.tsx +++ b/app/patient/schedule/page.tsx @@ -1,557 +1,12 @@ -"use client"; - -import { usersService } from "services/usersApi.mjs"; -import { doctorsService } from "services/doctorsApi.mjs"; -import { appointmentsService } from "services/appointmentsApi.mjs"; -import { AvailabilityService } from "services/availabilityApi.mjs"; -import { Calendar as CalendarShadcn } from "@/components/ui/calendar"; -import { format, addDays } from "date-fns"; - -import { useState, useEffect, useCallback, useRef } from "react"; -import { Calendar, Clock, User, StickyNote } from "lucide-react"; +// app/patient/appointments/page.tsx import PatientLayout from "@/components/patient-layout"; -import {Card,CardContent,CardHeader,CardTitle,} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Label } from "@/components/ui/label"; -import {Select,SelectContent,SelectItem,SelectTrigger,SelectValue,} from "@/components/ui/select"; -import { Textarea } from "@/components/ui/textarea"; -import { toast } from "@/hooks/use-toast"; +import ScheduleForm from "@/components/schedule/schedule-form"; -interface Doctor { - id: string; - full_name: string; - specialty: string; -} - -interface Disponibilidade { - id?: string; - doctor_id?: string; - weekday: string; - start_time: string; - end_time: string; - slot_minutes?: number; -} - -export default function ScheduleAppointment() { - const [selectedDoctor, setSelectedDoctor] = useState(""); - const [selectedDate, setSelectedDate] = useState(""); - const [selectedTime, setSelectedTime] = useState(""); - const [doctors, setDoctors] = useState([]); - const [availableTimes, setAvailableTimes] = useState([]); - const [loadingSlots, setLoadingSlots] = useState(false); - const [loadingDoctors, setLoadingDoctors] = useState(true); - const [tipoConsulta, setTipoConsulta] = useState("presencial"); - const [duracao, setDuracao] = useState("30"); - const [notes, setNotes] = useState(""); - const [disponibilidades, setDisponibilidades] = useState([]); - const [availableWeekdays, setAvailableWeekdays] = useState([]); // 1..7 - const [availabilityCounts, setAvailabilityCounts] = useState>({}); // "yyyy-MM-dd" -> count - - const calendarRef = useRef(null); - const tooltipRef = useRef(null); - const [tooltip, setTooltip] = useState<{ x: number; y: number; text: string } | null>(null); - - // --- Helpers --- - const getWeekdayNumber = (weekday: string) => - ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] - .indexOf(weekday.toLowerCase()) + 1; // monday=1 ... sunday=7 - - const getBrazilDate = (date: Date) => - new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0)); - - // --- Fetch doctors --- - const fetchDoctors = useCallback(async () => { - setLoadingDoctors(true); - try { - const data: Doctor[] = await doctorsService.list(); - setDoctors(data || []); - } catch (e) { - console.error("Erro ao buscar médicos:", e); - toast({ title: "Erro", description: "Não foi possível carregar médicos." }); - } finally { - setLoadingDoctors(false); - } - }, []); - - // --- Load disponibilidades details for selected doctor and compute weekdays --- - const loadDoctorDisponibilidades = useCallback(async (doctorId?: string) => { - if (!doctorId) { - setDisponibilidades([]); - setAvailableWeekdays([]); - setAvailabilityCounts({}); - return; - } - - try { - const disp: Disponibilidade[] = await AvailabilityService.listById(doctorId); - setDisponibilidades(disp || []); - const nums = (disp || []).map((d) => getWeekdayNumber(d.weekday)).filter(Boolean); - setAvailableWeekdays(Array.from(new Set(nums))); - // compute counts preview for next 90 days - await computeAvailabilityCountsPreview(doctorId, disp || []); - } catch (e) { - console.error("Erro disponibilidades:", e); - setDisponibilidades([]); - setAvailableWeekdays([]); - setAvailabilityCounts({}); - } - }, []); - - const computeAvailabilityCountsPreview = async (doctorId: string, dispList: Disponibilidade[]) => { - try { - const today = new Date(); - const start = format(today, "yyyy-MM-dd"); - const endDate = addDays(today, 90); - const end = format(endDate, "yyyy-MM-dd"); - - // fetch appointments for this doctor for the whole window (one call) - const appointments = await appointmentsService.search_appointment( - `doctor_id=eq.${doctorId}&scheduled_at=gte.${start}T00:00:00Z&scheduled_at=lt.${end}T23:59:59Z` - ); - - // group appointments by date - const apptsByDate: Record = {}; - (appointments || []).forEach((a: any) => { - const d = String(a.scheduled_at).split("T")[0]; - apptsByDate[d] = (apptsByDate[d] || 0) + 1; - }); - - const counts: Record = {}; - for (let i = 0; i <= 90; i++) { - const d = addDays(today, i); - const key = format(d, "yyyy-MM-dd"); - const dayOfWeek = d.getDay() === 0 ? 7 : d.getDay(); // 1..7 - - // find all disponibilidades matching this weekday - const dailyDisp = dispList.filter((p) => getWeekdayNumber(p.weekday) === dayOfWeek); - - if (dailyDisp.length === 0) { - counts[key] = 0; - continue; - } - - // compute total possible slots for the day summing multiple intervals - let possible = 0; - dailyDisp.forEach((p) => { - const [sh, sm] = p.start_time.split(":").map(Number); - const [eh, em] = p.end_time.split(":").map(Number); - const startMin = sh * 60 + sm; - const endMin = eh * 60 + em; - const slot = p.slot_minutes || 30; - // inclusive handling: if start==end -> 1 slot? normally not, we do Math.floor((end - start)/slot) + 1 if end >= start - if (endMin >= startMin) { - possible += Math.floor((endMin - startMin) / slot) + 1; - } - }); - - const occupied = apptsByDate[key] || 0; - const free = Math.max(0, possible - occupied); - counts[key] = free; - } - - setAvailabilityCounts(counts); - } catch (e) { - console.error("Erro ao calcular contagens de disponibilidade:", e); - setAvailabilityCounts({}); - } - }; - - // --- When doctor changes --- - useEffect(() => { - fetchDoctors(); - }, [fetchDoctors]); - - useEffect(() => { - if (selectedDoctor) { - loadDoctorDisponibilidades(selectedDoctor); - } else { - setDisponibilidades([]); - setAvailableWeekdays([]); - setAvailabilityCounts({}); - } - setSelectedDate(""); - setSelectedTime(""); - setAvailableTimes([]); - }, [selectedDoctor, loadDoctorDisponibilidades]); - - // --- Fetch available times for date --- (same logic, but shows toast if none) - const fetchAvailableSlots = useCallback( - async (doctorId: string, date: string) => { - if (!doctorId || !date) return; - setLoadingSlots(true); - setAvailableTimes([]); - - try { - const disponibilidades: Disponibilidade[] = await AvailabilityService.listById(doctorId); - - const consultas = await appointmentsService.search_appointment( - `doctor_id=eq.${doctorId}&scheduled_at=gte.${date}T00:00:00Z&scheduled_at=lt.${date}T23:59:59Z` - ); - - const diaJS = new Date(date).getDay(); // 0..6 - const diaAPI = diaJS === 0 ? 7 : diaJS; - - const disponibilidadeDia = disponibilidades.find( - (d) => getWeekdayNumber(d.weekday) === diaAPI - ); - - if (!disponibilidadeDia) { - setAvailableTimes([]); - toast({ title: "Nenhuma disponibilidade", description: "Nenhuma disponibilidade cadastrada para este dia." }); - setLoadingSlots(false); - return; - } - - const [startHour, startMin] = disponibilidadeDia.start_time.split(":").map(Number); - const [endHour, endMin] = disponibilidadeDia.end_time.split(":").map(Number); - const slot = disponibilidadeDia.slot_minutes || 30; - - const horariosGerados: string[] = []; - let atual = new Date(date); - atual.setHours(startHour, startMin, 0, 0); - - const end = new Date(date); - end.setHours(endHour, endMin, 0, 0); - - while (atual <= end) { - horariosGerados.push(atual.toTimeString().slice(0, 5)); - atual = new Date(atual.getTime() + slot * 60000); - } - - const ocupados = (consultas || []).map((c: any) => - String(c.scheduled_at).split("T")[1]?.slice(0, 5) - ); - - const livres = horariosGerados.filter((h) => !ocupados.includes(h)); - - if (livres.length === 0) { - toast({ title: "Sem horários livres", description: "Todos os horários estão ocupados neste dia." }); - } - - setAvailableTimes(livres); - } catch (err) { - console.error(err); - setAvailableTimes([]); - toast({ title: "Erro", description: "Falha ao carregar horários." }); - } finally { - setLoadingSlots(false); - } - }, - [] - ); - - // run fetchAvailableSlots when date changes - useEffect(() => { - if (selectedDoctor && selectedDate) { - fetchAvailableSlots(selectedDoctor, selectedDate); - } else { - setAvailableTimes([]); - } - setSelectedTime(""); - }, [selectedDoctor, selectedDate, fetchAvailableSlots]); - - // --- Submit --- - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!selectedDoctor || !selectedDate || !selectedTime) { - toast({ title: "Preencha os campos", description: "Selecione médico, data e horário." }); - return; - } - - try { - const doctor = doctors.find((d) => d.id === selectedDoctor); - const paciente = await usersService.getMe(); - - const body = { - doctor_id: doctor?.id, - patient_id: paciente.user.id, - scheduled_at: `${selectedDate}T${selectedTime}:00`, // saving as local-ish string (you chose UTC elsewhere) - duration_minutes: Number(duracao), - notes, - appointment_type: tipoConsulta, - }; - - await appointmentsService.create(body); - toast({ title: "Agendado", description: "Consulta agendada com sucesso." }); - - // reset - setSelectedDoctor(""); - setSelectedDate(""); - setSelectedTime(""); - setAvailableTimes([]); - setNotes(""); - // refresh counts - if (selectedDoctor) computeAvailabilityCountsPreview(selectedDoctor, disponibilidades); - } catch (err) { - console.error(err); - toast({ title: "Erro", description: "Falha ao agendar consulta." }); - } - }; - - // --- Calendar tooltip via event delegation --- - useEffect(() => { - const cont = calendarRef.current; - if (!cont) return; - - const onMove = (ev: MouseEvent) => { - const target = ev.target as HTMLElement | null; - if (!target) return; - // find closest button that likely is a day cell - const btn = target.closest("button"); - if (!btn) { - setTooltip(null); - return; - } - // many calendar implementations put the date in aria-label, e.g. "November 13, 2025" - const aria = btn.getAttribute("aria-label") || btn.textContent || ""; - // try to parse date from aria-label: new Date(aria) works for many locales - const parsed = new Date(aria); - if (isNaN(parsed.getTime())) { - // sometimes aria-label is like "13" (just day) - try data-day attribute - const dataDay = btn.getAttribute("data-day"); - if (dataDay) { - // try parse yyyy-mm-dd - const pd = new Date(dataDay); - if (!isNaN(pd.getTime())) { - const key = format(pd, "yyyy-MM-dd"); - const count = availabilityCounts[key] ?? 0; - setTooltip({ - x: ev.pageX + 10, - y: ev.pageY + 10, - text: `${count} horário${count !== 1 ? "s" : ""} disponíveis`, - }); - return; - } - } - setTooltip(null); - return; - } - // parsed is valid - convert to yyyy-MM-dd - const key = format(getBrazilDate(parsed), "yyyy-MM-dd"); - const count = availabilityCounts[key] ?? 0; - setTooltip({ - x: ev.pageX + 10, - y: ev.pageY + 10, - text: `${count} horário${count !== 1 ? "s" : ""} disponíveis`, - }); - }; - - const onLeave = () => setTooltip(null); - - cont.addEventListener("mousemove", onMove); - cont.addEventListener("mouseleave", onLeave); - - return () => { - cont.removeEventListener("mousemove", onMove); - cont.removeEventListener("mouseleave", onLeave); - }; - }, [availabilityCounts]); - - +export default function PatientAppointments() { return ( -
-

Agendar Consulta

- - - - Dados da Consulta - - - -
- {/* LEFT */} -
-
- - -
- -
- -
- { - if (!date) return; - const fixedDate = new Date(date.getTime() + 12 * 60 * 60 * 1000); - const formatted = format(fixedDate, "yyyy-MM-dd"); - setSelectedDate(formatted); - }} - className="rounded-md border shadow-sm p-2" - modifiers={{ selected: selectedDate ? new Date(selectedDate + 'T12:00:00') : undefined }} - modifiersClassNames={{ - selected: - "bg-blue-600 text-white hover:bg-blue-700 rounded-md", - }} - /> - - - -
- -
- -
- -