From da35ebbff53212e896c417ce5c5c549b83e6bbd0 Mon Sep 17 00:00:00 2001 From: StsDanilo Date: Tue, 18 Nov 2025 15:09:36 -0300 Subject: [PATCH 1/2] =?UTF-8?q?Cria=C3=A7=C3=A3o=20de=20p=C3=A1gina=20de?= =?UTF-8?q?=20disponibilidade=20para=20o=20gestor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/doctor/dashboard/page.tsx | 159 +++++++++-------------- app/manager/disponibilidade/page.tsx | 185 +++++++++++++++++++++++++++ components/Sidebar.tsx | 3 +- components/ui/WeeklyScheduleCard.tsx | 105 +++++++++++++++ 4 files changed, 355 insertions(+), 97 deletions(-) create mode 100644 app/manager/disponibilidade/page.tsx create mode 100644 components/ui/WeeklyScheduleCard.tsx diff --git a/app/doctor/dashboard/page.tsx b/app/doctor/dashboard/page.tsx index 060d8e4..af05f6d 100644 --- a/app/doctor/dashboard/page.tsx +++ b/app/doctor/dashboard/page.tsx @@ -14,6 +14,7 @@ import { exceptionsService } from "@/services/exceptionApi.mjs"; import { doctorsService } from "@/services/doctorsApi.mjs"; import { usersService } from "@/services/usersApi.mjs"; import Sidebar from "@/components/Sidebar"; +import WeeklyScheduleCard from "@/components/ui/WeeklyScheduleCard"; type Availability = { id: string; @@ -35,33 +36,33 @@ type Schedule = { }; type Doctor = { - id: string; - user_id: string | null; - crm: string; - crm_uf: string; - specialty: string; - full_name: string; - cpf: string; - email: string; - phone_mobile: string | null; - phone2: string | null; - cep: string | null; - street: string | null; - number: string | null; - complement: string | null; - neighborhood: string | null; - city: string | null; - state: string | null; - birth_date: string | null; - rg: string | null; - active: boolean; - created_at: string; - updated_at: string; - created_by: string; - updated_by: string | null; - max_days_in_advance: number; - rating: number | null; -} + id: string; + user_id: string | null; + crm: string; + crm_uf: string; + specialty: string; + full_name: string; + cpf: string; + email: string; + phone_mobile: string | null; + phone2: string | null; + cep: string | null; + street: string | null; + number: string | null; + complement: string | null; + neighborhood: string | null; + city: string | null; + state: string | null; + birth_date: string | null; + rg: string | null; + active: boolean; + created_at: string; + updated_at: string; + created_by: string; + updated_by: string | null; + max_days_in_advance: number; + rating: number | null; +}; interface UserPermissions { isAdmin: boolean; @@ -94,15 +95,15 @@ interface UserData { } interface Exception { - id: string; // id da exceção - doctor_id: string; - date: string; // formato YYYY-MM-DD - start_time: string | null; // null = dia inteiro - end_time: string | null; // null = dia inteiro - kind: "bloqueio" | "disponibilidade"; // tipos conhecidos - reason: string | null; // pode ser null - created_at: string; // timestamp ISO - created_by: string; + id: string; // id da exceção + doctor_id: string; + date: string; // formato YYYY-MM-DD + start_time: string | null; // null = dia inteiro + end_time: string | null; // null = dia inteiro + kind: "bloqueio" | "disponibilidade"; // tipos conhecidos + reason: string | null; // pode ser null + created_at: string; // timestamp ISO + created_by: string; } export default function PatientDashboard() { @@ -128,44 +129,38 @@ export default function PatientDashboard() { }; useEffect(() => { - const fetchData = async () => { - try { - const doctorsList: Doctor[] = await doctorsService.list(); - const doctor = doctorsList[0]; + const fetchData = async () => { + try { + const doctorsList: Doctor[] = await doctorsService.list(); + const doctor = doctorsList[0]; - // Salva no estado - setLoggedDoctor(doctor); + // Salva no estado + setLoggedDoctor(doctor); - // Busca disponibilidade - const availabilityList = await AvailabilityService.list(); - - // Filtra já com a variável local - const filteredAvail = availabilityList.filter( - (disp: { doctor_id: string }) => disp.doctor_id === doctor?.id - ); - setAvailability(filteredAvail); + // Busca disponibilidade + const availabilityList = await AvailabilityService.list(); - // Busca exceções - const exceptionsList = await exceptionsService.list(); - const filteredExc = exceptionsList.filter( - (exc: { doctor_id: string }) => exc.doctor_id === doctor?.id - ); - console.log(exceptionsList) - setExceptions(filteredExc); + // Filtra já com a variável local + const filteredAvail = availabilityList.filter((disp: { doctor_id: string }) => disp.doctor_id === doctor?.id); + setAvailability(filteredAvail); - } catch (e: any) { - alert(`${e?.error} ${e?.message}`); - } - }; + // Busca exceções + const exceptionsList = await exceptionsService.list(); + const filteredExc = exceptionsList.filter((exc: { doctor_id: string }) => exc.doctor_id === doctor?.id); + setExceptions(filteredExc); + } catch (e: any) { + alert(`${e?.error} ${e?.message}`); + } + }; - fetchData(); -}, []); + fetchData(); + }, []); // Função auxiliar para filtrar o id do doctor correspondente ao user logado function findDoctorById(id: string, doctors: Doctor[]) { return doctors.find((doctor) => doctor.user_id === id); } - + const openDeleteDialog = (exceptionId: string) => { setExceptionToDelete(exceptionId); setDeleteDialogOpen(true); @@ -173,7 +168,7 @@ export default function PatientDashboard() { const handleDeleteException = async (ExceptionId: string) => { try { - alert(ExceptionId) + alert(ExceptionId); const res = await exceptionsService.delete(ExceptionId); let message = "Exceção deletada com sucesso"; @@ -316,31 +311,7 @@ export default function PatientDashboard() { Horário Semanal Confira rapidamente a sua disponibilidade da semana - - {["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"].map((day) => { - const times = schedule[day] || []; - return ( -
-
-
-

{weekdaysPT[day]}

-
-
- {times.length > 0 ? ( - times.map((t, i) => ( -

- {formatTime(t.start)}
{formatTime(t.end)} -

- )) - ) : ( -

Sem horário

- )} -
-
-
- ); - })} -
+ {loggedDoctor && }
@@ -358,7 +329,7 @@ export default function PatientDashboard() { weekday: "long", day: "2-digit", month: "long", - timeZone: "UTC" + timeZone: "UTC", }); const startTime = formatTime(ex.start_time); @@ -369,11 +340,7 @@ export default function PatientDashboard() {

{date}

-

- {startTime && endTime - ? `${startTime} - ${endTime}` - : "Dia todo"} -

+

{startTime && endTime ? `${startTime} - ${endTime}` : "Dia todo"}

{ex.kind === "bloqueio" ? "Bloqueio" : "Liberação"}

diff --git a/app/manager/disponibilidade/page.tsx b/app/manager/disponibilidade/page.tsx new file mode 100644 index 0000000..adfcd80 --- /dev/null +++ b/app/manager/disponibilidade/page.tsx @@ -0,0 +1,185 @@ +"use client"; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import Sidebar from "@/components/Sidebar"; +import WeeklyScheduleCard from "@/components/ui/WeeklyScheduleCard"; + +import { useEffect, useState, useMemo } from "react"; + +import { AvailabilityService } from "@/services/availabilityApi.mjs"; +import { doctorsService } from "@/services/doctorsApi.mjs"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Button } from "@/components/ui/button"; +import { Filter } from "lucide-react"; + +type Doctor = { + id: string; + full_name: string; + specialty: string; + active: boolean; +}; + +type Availability = { + id: string; + doctor_id: string; + weekday: string; + start_time: string; + end_time: string; +}; + +export default function AllAvailabilities() { + const [availabilities, setAvailabilities] = useState(null); + const [doctors, setDoctors] = useState(null); + + // 🔎 Filtros + const [search, setSearch] = useState(""); + const [specialty, setSpecialty] = useState("all"); + + // 🔄 Paginação + const ITEMS_PER_PAGE = 6; + const [page, setPage] = useState(1); + + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchData = async () => { + try { + const doctorsList = await doctorsService.list(); + setDoctors(doctorsList); + + const availabilityList = await AvailabilityService.list(); + setAvailabilities(availabilityList); + } catch (e: any) { + alert(`${e?.error} ${e?.message}`); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + // 🎯 Obter todas as especialidades existentes + const specialties = useMemo(() => { + if (!doctors) return []; + const unique = Array.from(new Set(doctors.map((d) => d.specialty))); + return unique; + }, [doctors]); + + // 🔍 Filtrar médicos por especialidade + nome + const filteredDoctors = useMemo(() => { + if (!doctors) return []; + + return doctors.filter((doctor) => (specialty === "all" ? true : doctor.specialty === specialty)).filter((doctor) => doctor.full_name.toLowerCase().includes(search.toLowerCase())); + }, [doctors, search, specialty]); + + // 📄 Paginação (após filtros!) + const totalPages = Math.ceil(filteredDoctors.length / ITEMS_PER_PAGE); + const paginatedDoctors = filteredDoctors.slice((page - 1) * ITEMS_PER_PAGE, page * ITEMS_PER_PAGE); + + const goNext = () => setPage((p) => Math.min(p + 1, totalPages)); + const goPrev = () => setPage((p) => Math.max(p - 1, 1)); + + if (loading) { + return ( + +
Carregando dados...
+
+ ); + } + + if (!doctors || !availabilities) { + return ( + +
Não foi possível carregar médicos ou disponibilidades.
+
+ ); + } + + return ( + +
+
+

Disponibilidade dos Médicos

+

Visualize a agenda semanal individual de cada médico.

+
+ + + {/* 🔎 Filtros */} +
+ {/* Filtro por nome */} + + { + setSearch(e.target.value); + setPage(1); + }} + className="w-full md:w-1/3" + /> + + {/* Filtro por especialidade */} + +
+
+
+ {/* GRID de cards */} +
+ {paginatedDoctors.map((doctor) => { + const doctorAvailabilities = availabilities.filter((a) => a.doctor_id === doctor.id); + + return ( + + + {doctor.full_name} + + + + + + + ); + })} +
+ + {/* 📄 Paginação */} + {totalPages > 1 && ( +
+ + + + Página {page} de {totalPages} + + + +
+ )} +
+
+ ); +} diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 577d49c..c135d91 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -178,7 +178,8 @@ export default function Sidebar({ children }: SidebarProps) { { href: "/manager/usuario", icon: Users, label: "Gestão de Usuários" }, { href: "/manager/home", icon: Stethoscope, label: "Gestão de Médicos" }, { href: "/manager/pacientes", icon: Users, label: "Gestão de Pacientes" }, - { href: "/secretary/appointments", icon: CalendarCheck2, label: "Consultas" }, //adicionar botão de voltar pra pagina anterior + { href: "/secretary/appointments", icon: CalendarCheck2, label: "Consultas" }, + { href: "/manager/disponibilidade", icon: ClipboardList, label: "Disponibilidade" }, ]; let menuItems: MenuItem[]; diff --git a/components/ui/WeeklyScheduleCard.tsx b/components/ui/WeeklyScheduleCard.tsx new file mode 100644 index 0000000..2b33ba0 --- /dev/null +++ b/components/ui/WeeklyScheduleCard.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; +import { AvailabilityService } from "@/services/availabilityApi.mjs"; +import { doctorsService } from "@/services/doctorsApi.mjs"; + +type Availability = { + id: string; + doctor_id: string; + weekday: string; + start_time: string; + end_time: string; + slot_minutes: number; + appointment_type: string; + active: boolean; + created_at: string; + updated_at: string; + created_by: string; + updated_by: string | null; +}; + +interface WeeklyScheduleProps { + doctorId?: string; +} + +export default function WeeklyScheduleCard({ doctorId }: WeeklyScheduleProps) { + const [schedule, setSchedule] = useState>({}); + const [loading, setLoading] = useState(true); + + const weekdaysPT: Record = { + sunday: "Domingo", + monday: "Segunda", + tuesday: "Terça", + wednesday: "Quarta", + thursday: "Quinta", + friday: "Sexta", + saturday: "Sábado", + }; + + const formatTime = (time?: string | null) => time?.split(":")?.slice(0, 2).join(":") ?? ""; + + function formatAvailability(data: Availability[]) { + const grouped = data.reduce((acc: any, item) => { + const { weekday, start_time, end_time } = item; + + if (!acc[weekday]) acc[weekday] = []; + + acc[weekday].push({ start: start_time, end: end_time }); + + return acc; + }, {}); + + return grouped; + } + + useEffect(() => { + const fetchSchedule = async () => { + try { + const availabilityList = await AvailabilityService.list(); + + const filtered = availabilityList.filter((a: Availability) => a.doctor_id == doctorId); + + const formatted = formatAvailability(filtered); + setSchedule(formatted); + } catch (err) { + console.error("Erro ao carregar horários:", err); + } finally { + setLoading(false); + } + }; + + fetchSchedule(); + }, []); + + return ( +
+ {loading ? ( +

Carregando...

+ ) : ( + ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "Saturday"].map((day) => { + const times = schedule[day] || []; + return ( +
+
+

{weekdaysPT[day]}

+
+ {times.length > 0 ? ( + times.map((t, i) => ( +

+ {formatTime(t.start)}
{formatTime(t.end)} +

+ )) + ) : ( +

Sem horário

+ )} +
+
+
+ ); + }) + )} +
+ ); +} From 945ec9d7e7819b606554f87b5f2a7400ba29338c Mon Sep 17 00:00:00 2001 From: StsDanilo Date: Mon, 24 Nov 2025 16:53:16 -0300 Subject: [PATCH 2/2] Update Disponibilidade e agendamento --- app/doctor/consultas/page.tsx | 23 +++++++++++----- app/doctor/disponibilidade/page.tsx | 42 +++++++++++++++-------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/app/doctor/consultas/page.tsx b/app/doctor/consultas/page.tsx index 9332a8b..8eca4d2 100644 --- a/app/doctor/consultas/page.tsx +++ b/app/doctor/consultas/page.tsx @@ -31,7 +31,7 @@ interface EnrichedAppointment { } export default function DoctorAppointmentsPage() { - const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: 'medico' }); + const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: "medico" }); const [allAppointments, setAllAppointments] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -111,13 +111,22 @@ export default function DoctorAppointmentsPage() { return format(date, "EEEE, dd 'de' MMMM", { locale: ptBR }); }; + const statusPT: Record = { + confirmed: "Confirmada", + completed: "Concluída", + cancelled: "Cancelada", + requested: "Solicitada", + no_show: "oculta", + checked_in: "Aguardando", + }; + const getStatusVariant = (status: EnrichedAppointment['status']) => { switch (status) { - case "confirmed": case "checked_in": return "default"; - case "completed": return "secondary"; - case "cancelled": case "no_show": return "destructive"; - case "requested": return "outline"; - default: return "outline"; + case "confirmed": case "checked_in": return "text-foreground bg-blue-100 hover:bg-blue-150"; + case "completed": return "text-foreground bg-green-100 hover:bg-green-150"; + case "cancelled": case "no_show": return "text-foreground bg-red-200 hover:bg-red-250"; + case "requested": return "text-foreground bg-yellow-100 hover:bg-yellow-150"; + default: return "border-gray bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90"; } }; @@ -191,7 +200,7 @@ export default function DoctorAppointmentsPage() { {/* Coluna 2: Status e Telefone */}
- {appointment.status.replace('_', ' ')} + {statusPT[appointment.status].replace('_', ' ')}
{appointment.patientPhone} diff --git a/app/doctor/disponibilidade/page.tsx b/app/doctor/disponibilidade/page.tsx index 80b7816..831b368 100644 --- a/app/doctor/disponibilidade/page.tsx +++ b/app/doctor/disponibilidade/page.tsx @@ -183,25 +183,25 @@ export default function AvailabilityPage() { saturday: "Sábado", }; const fetchData = async () => { - try { - const loggedUser = await usersService.getMe(); - const doctorList = await doctorsService.list(); - setUserData(loggedUser); - const doctor = findDoctorById(loggedUser.user.id, doctorList); - setDoctorId(doctor?.id); - console.log(doctor); - // Busca disponibilidade - const availabilityList = await AvailabilityService.list(); - - // Filtra já com a variável local - const filteredAvail = availabilityList.filter( - (disp: { doctor_id: string }) => disp.doctor_id === doctor?.id - ); - setAvailability(filteredAvail); - } catch (e: any) { - alert(`${e?.error} ${e?.message}`); - } - }; + try { + const loggedUser = await usersService.getMe(); + const doctorList = await doctorsService.list(); + setUserData(loggedUser); + const doctor = findDoctorById(loggedUser.user.id, doctorList); + setDoctorId(doctor?.id); + console.log(doctor); + // Busca disponibilidade + const availabilityList = await AvailabilityService.list(); + + // Filtra já com a variável local + const filteredAvail = availabilityList.filter( + (disp: { doctor_id: string }) => disp.doctor_id === doctor?.id + ); + setAvailability(filteredAvail); + } catch (e: any) { + alert(`${e?.error} ${e?.message}`); + } + }; useEffect(() => { fetchData(); @@ -284,6 +284,7 @@ export default function AvailabilityPage() { description: err?.message || "Não foi possível criar a disponibilidade", }); } finally { + fetchData() setIsLoading(false); } }; @@ -318,6 +319,7 @@ export default function AvailabilityPage() { description: e?.message || "Não foi possível deletar a disponibilidade", }); } + fetchData() setDeleteDialogOpen(false); setSelectedAvailability(null); }; @@ -453,7 +455,7 @@ export default function AvailabilityPage() {
-

+

{formatTime(t.start)} - {formatTime(t.end)}