diff --git a/app/doctor/consultas/page.tsx b/app/doctor/consultas/page.tsx index b8edfda..9332a8b 100644 --- a/app/doctor/consultas/page.tsx +++ b/app/doctor/consultas/page.tsx @@ -1,272 +1,229 @@ +// ARQUIVO COMPLETO COM A INTERFACE CORRIGIDA: app/doctor/consultas/page.tsx + "use client"; import type React from "react"; -import { useState, useEffect } from "react"; -import DoctorLayout from "@/components/doctor-layout"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useState, useEffect, useMemo } from "react"; +import { useAuthLayout } from "@/hooks/useAuthLayout"; +import { appointmentsService } from "@/services/appointmentsApi.mjs"; +import { patientsService } from "@/services/patientsApi.mjs"; +import { doctorsService } from "@/services/doctorsApi.mjs"; + +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { Clock, Calendar as CalendarIcon, MapPin, Phone, User, X, RefreshCw } from "lucide-react"; import { Badge } from "@/components/ui/badge"; +import { Calendar as CalendarShadcn } from "@/components/ui/calendar"; +import { Separator } from "@/components/ui/separator"; +import { Clock, Calendar as CalendarIcon, User, X, RefreshCw, Loader2, MapPin, Phone, List } from "lucide-react"; +import { format, isFuture, parseISO, isValid, isToday, isTomorrow } from "date-fns"; +import { ptBR } from "date-fns/locale"; import { toast } from "sonner"; +import Sidebar from "@/components/Sidebar"; -// IMPORTAR O COMPONENTE CALENDÁRIO DA SHADCN -import { Calendar } from "@/components/ui/calendar"; -import { format } from "date-fns"; // Usaremos o date-fns para formatação e comparação de datas - -const APPOINTMENTS_STORAGE_KEY = "clinic-appointments"; - -// --- TIPAGEM DA CONSULTA SALVA NO LOCALSTORAGE --- -interface LocalStorageAppointment { - id: number; - patientName: string; - doctor: string; - specialty: string; - date: string; // Data no formato YYYY-MM-DD - time: string; // Hora no formato HH:MM - status: "agendada" | "confirmada" | "cancelada" | "realizada"; - location: string; - phone: string; +// Interfaces (sem alteração) +interface EnrichedAppointment { + id: string; + patientName: string; + patientPhone: string; + scheduled_at: string; + status: "requested" | "confirmed" | "completed" | "cancelled" | "checked_in" | "no_show"; + location: string; } -const LOGGED_IN_DOCTOR_NAME = "Dr. João Santos"; - -// Função auxiliar para comparar se duas datas (Date objects) são o mesmo dia -const isSameDay = (date1: Date, date2: Date) => { - return date1.getFullYear() === date2.getFullYear() && - date1.getMonth() === date2.getMonth() && - date1.getDate() === date2.getDate(); -}; - -// --- COMPONENTE PRINCIPAL --- - export default function DoctorAppointmentsPage() { - const [allAppointments, setAllAppointments] = useState([]); - const [filteredAppointments, setFilteredAppointments] = useState([]); - const [isLoading, setIsLoading] = useState(true); + const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: 'medico' }); + + const [allAppointments, setAllAppointments] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedDate, setSelectedDate] = useState(new Date()); - // NOVO ESTADO 1: Armazena os dias com consultas (para o calendário) - const [bookedDays, setBookedDays] = useState([]); + const fetchAppointments = async (authUserId: string) => { + setIsLoading(true); + try { + const allDoctors = await doctorsService.list(); + const currentDoctor = allDoctors.find((doc: any) => doc.user_id === authUserId); + if (!currentDoctor) { + toast.error("Perfil de médico não encontrado para este usuário."); + return setIsLoading(false); + } + const doctorId = currentDoctor.id; - // NOVO ESTADO 2: Armazena a data selecionada no calendário - const [selectedCalendarDate, setSelectedCalendarDate] = useState(new Date()); + const [appointmentsList, patientsList] = await Promise.all([ + appointmentsService.search_appointment(`doctor_id=eq.${doctorId}&order=scheduled_at.asc`), + patientsService.list() + ]); - useEffect(() => { - loadAppointments(); - }, []); + const patientsMap = new Map( + patientsList.map((p: any) => [p.id, { name: p.full_name, phone: p.phone_mobile }]) + ); + + const enrichedAppointments = appointmentsList.map((apt: any) => ({ + id: apt.id, + patientName: patientsMap.get(apt.patient_id)?.name || "Paciente Desconhecido", + patientPhone: patientsMap.get(apt.patient_id)?.phone || "N/A", + scheduled_at: apt.scheduled_at, + status: apt.status, + location: "Consultório Principal", + })); - // Efeito para filtrar a lista sempre que o calendário ou a lista completa for atualizada - useEffect(() => { - if (selectedCalendarDate) { - const dateString = format(selectedCalendarDate, 'yyyy-MM-dd'); + setAllAppointments(enrichedAppointments); + } catch (error) { + console.error("Erro ao carregar a agenda:", error); + toast.error("Não foi possível carregar sua agenda."); + } finally { + setIsLoading(false); + } + }; - // Filtra a lista completa de agendamentos pela data selecionada - const todayAppointments = allAppointments - .filter(app => app.date === dateString) - .sort((a, b) => a.time.localeCompare(b.time)); // Ordena por hora + useEffect(() => { + if (user?.id) { + fetchAppointments(user.id); + } + }, [user]); - setFilteredAppointments(todayAppointments); - } else { - // Se nenhuma data estiver selecionada (ou se for limpa), mostra todos (ou os de hoje) - const todayDateString = format(new Date(), 'yyyy-MM-dd'); - const todayAppointments = allAppointments - .filter(app => app.date === todayDateString) - .sort((a, b) => a.time.localeCompare(b.time)); + const groupedAppointments = useMemo(() => { + const appointmentsToDisplay = selectedDate + ? allAppointments.filter(app => app.scheduled_at && app.scheduled_at.startsWith(format(selectedDate, "yyyy-MM-dd"))) + : allAppointments.filter(app => { + if (!app.scheduled_at) return false; + const dateObj = parseISO(app.scheduled_at); + return isValid(dateObj) && isFuture(dateObj); + }); - setFilteredAppointments(todayAppointments); - } - }, [allAppointments, selectedCalendarDate]); + return appointmentsToDisplay.reduce((acc, appointment) => { + const dateKey = format(parseISO(appointment.scheduled_at), "yyyy-MM-dd"); + if (!acc[dateKey]) acc[dateKey] = []; + acc[dateKey].push(appointment); + return acc; + }, {} as Record); + }, [allAppointments, selectedDate]); - const loadAppointments = () => { - setIsLoading(true); - try { - const storedAppointmentsRaw = localStorage.getItem(APPOINTMENTS_STORAGE_KEY); - const allAppts: LocalStorageAppointment[] = storedAppointmentsRaw ? JSON.parse(storedAppointmentsRaw) : []; + const bookedDays = useMemo(() => { + return allAppointments + .map(app => app.scheduled_at ? new Date(app.scheduled_at) : null) + .filter((date): date is Date => date !== null); + }, [allAppointments]); - // ***** NENHUM FILTRO POR MÉDICO AQUI (Como solicitado) ***** - const appointmentsToShow = allAppts; + const formatDisplayDate = (dateString: string) => { + const date = parseISO(dateString); + if (isToday(date)) return `Hoje, ${format(date, "dd 'de' MMMM", { locale: ptBR })}`; + if (isTomorrow(date)) return `Amanhã, ${format(date, "dd 'de' MMMM", { locale: ptBR })}`; + return format(date, "EEEE, dd 'de' MMMM", { locale: ptBR }); + }; - // 1. EXTRAI E PREPARA AS DATAS PARA O CALENDÁRIO - const uniqueBookedDates = Array.from(new Set(appointmentsToShow.map(app => app.date))); + 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"; + } + }; - // Converte YYYY-MM-DD para objetos Date, garantindo que o tempo seja meia-noite (00:00:00) - const dateObjects = uniqueBookedDates.map(dateString => new Date(dateString + 'T00:00:00')); + const handleCancel = async (id: string) => { + // ... (função sem alteração) + }; + const handleReSchedule = (id: string) => { + // ... (função sem alteração) + }; - setAllAppointments(appointmentsToShow); - setBookedDays(dateObjects); - toast.success("Agenda atualizada com sucesso!"); - } catch (error) { - console.error("Erro ao carregar a agenda do LocalStorage:", error); - toast.error("Não foi possível carregar sua agenda."); - } finally { - setIsLoading(false); - } - }; + if (isAuthLoading) { + return
Carregando...
; + } - const getStatusVariant = (status: LocalStorageAppointment['status']) => { - // ... (código mantido) - switch (status) { - case "confirmada": - case "agendada": - return "default"; - case "realizada": - return "secondary"; - case "cancelada": - return "destructive"; - default: - return "outline"; - } - }; + return ( + +
+
+

Agenda Médica

+

Consultas para {user?.name || "você"}

+
+
+

+ {selectedDate ? `Agenda de ${format(selectedDate, "dd/MM/yyyy")}` : "Próximas Consultas"} +

+
+ + +
+
+
+
+ + Filtrar por DataSelecione um dia para ver os detalhes. + + + + +
+
+ {isLoading ? ( +
+ ) : Object.keys(groupedAppointments).length === 0 ? ( + + Nenhuma consulta encontrada +

{selectedDate ? "Não há agendamentos para esta data." : "Não há próximas consultas agendadas."}

+
+ ) : ( + Object.entries(groupedAppointments).map(([date, appointmentsForDay]) => ( +
+

{formatDisplayDate(date)}

+
+ {appointmentsForDay.map((appointment) => { + const showActions = appointment.status === "requested" || appointment.status === "confirmed"; + const scheduledAtDate = parseISO(appointment.scheduled_at); + return ( + // *** INÍCIO DA MUDANÇA NO CARD *** + + + {/* Coluna 1: Nome e Hora */} +
+
+ + {appointment.patientName} +
+
+ + {format(scheduledAtDate, "HH:mm")} +
+
+ + {/* Coluna 2: Status e Telefone */} +
+ {appointment.status.replace('_', ' ')} +
+ + {appointment.patientPhone} +
+
- const handleCancel = (id: number) => { - // ... (código mantido para cancelamento) - const storedAppointmentsRaw = localStorage.getItem(APPOINTMENTS_STORAGE_KEY); - const allAppts: LocalStorageAppointment[] = storedAppointmentsRaw ? JSON.parse(storedAppointmentsRaw) : []; - - const updatedAppointments = allAppts.map(app => - app.id === id ? { ...app, status: "cancelada" as const } : app - ); - - localStorage.setItem(APPOINTMENTS_STORAGE_KEY, JSON.stringify(updatedAppointments)); - loadAppointments(); - toast.info(`Consulta cancelada com sucesso.`); - }; - - const handleReSchedule = (id: number) => { - toast.info(`Reagendamento da Consulta ID: ${id}. Navegar para a página de agendamento.`); - }; - - const displayDate = selectedCalendarDate ? - new Date(selectedCalendarDate).toLocaleDateString("pt-BR", { weekday: 'long', day: '2-digit', month: 'long' }) : - "Selecione uma data"; - - - return ( - -
-
-

Agenda Médica Centralizada

-

Todas as consultas do sistema são exibidas aqui ({LOGGED_IN_DOCTOR_NAME})

-
- -
-

Consultas para: {displayDate}

- -
- - {/* NOVO LAYOUT DE DUAS COLUNAS */} -
- - {/* COLUNA 1: CALENDÁRIO */} -
- - - - - Calendário - -

Dias em azul possuem agendamentos.

-
- - - + {/* Coluna 3: Ações */} +
+ {showActions && ( +
+ + +
+ )} +
+
-
- - {/* COLUNA 2: LISTA DE CONSULTAS FILTRADAS */} -
- {isLoading ? ( -

Carregando a agenda...

- ) : filteredAppointments.length === 0 ? ( -

Nenhuma consulta encontrada para a data selecionada.

- ) : ( - filteredAppointments.map((appointment) => { - const showActions = appointment.status === "agendada" || appointment.status === "confirmada"; - - return ( - - - - - {appointment.patientName} - - - {appointment.status} - - - - - {/* Detalhes e Ações... (mantidos) */} -
-
- - Médico: {appointment.doctor} -
-
- - {new Date(appointment.date).toLocaleDateString("pt-BR", { timeZone: "UTC" })} -
-
- - {appointment.time} -
-
- -
-
- - {appointment.location} -
-
- - {appointment.phone || "N/A"} -
-
- -
- {showActions && ( -
- - -
- )} -
-
-
- ); - }) - )} -
+ // *** FIM DA MUDANÇA NO CARD *** + ); + })} +
+
-
- - ); + )) + )} +
+
+
+ + ); } \ No newline at end of file diff --git a/app/doctor/dashboard/page.tsx b/app/doctor/dashboard/page.tsx index cf9bad5..060d8e4 100644 --- a/app/doctor/dashboard/page.tsx +++ b/app/doctor/dashboard/page.tsx @@ -1,6 +1,5 @@ "use client"; -import DoctorLayout from "@/components/doctor-layout"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Calendar, Clock, User, Trash2 } from "lucide-react"; @@ -14,6 +13,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 +231,7 @@ export default function PatientDashboard() { }, [availability]); return ( - +

Dashboard

@@ -409,6 +409,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..80b7816 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,8 +323,8 @@ export default function AvailabilityPage() { }; return ( - -
+ +

Definir Disponibilidade

@@ -416,11 +416,12 @@ export default function AvailabilityPage() { {/* **AJUSTE DE RESPONSIVIDADE: BOTÕES DE AÇÃO** */} {/* Alinha à direita em telas maiores e empilha (com o botão primário no final) em telas menores */} + {/* Alteração aqui: Adicionado w-full aos Links e Buttons para ocuparem a largura total em telas pequenas */}
-
+
{/* Ajustado para empilhar os botões Cancelar e Salvar em telas pequenas */} @@ -506,6 +507,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..9c9b8a9 100644 --- a/app/doctor/medicos/[id]/laudos/novo/page.tsx +++ b/app/doctor/medicos/[id]/laudos/novo/page.tsx @@ -17,7 +17,7 @@ import { format } from "date-fns"; 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 +97,7 @@ export default function NovoLaudoPage() { }; return ( - +
@@ -189,6 +189,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..4dd6c87 100644 --- a/app/doctor/medicos/page.tsx +++ b/app/doctor/medicos/page.tsx @@ -1,368 +1,421 @@ -// app/doctor/pacientes/page.tsx (assumindo a localização) "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; - nome: string; - telefone: string; - cidade: string; - estado: string; - ultimoAtendimento?: string; - proximoAtendimento?: string; - email?: string; - birth_date?: string; - cpf?: string; - blood_type?: string; - weight_kg?: number; - height_m?: number; - street?: string; - number?: string; - complement?: string; - neighborhood?: string; - cep?: string; + id: string; + nome: string; + telefone: string; + cidade: string; + estado: string; + ultimoAtendimento?: string; + proximoAtendimento?: string; + email?: string; + birth_date?: string; + cpf?: string; + blood_type?: string; + weight_kg?: number; + height_m?: number; + street?: string; + number?: string; + complement?: string; + neighborhood?: string; + cep?: string; } export default function PacientesPage() { - const [pacientes, setPacientes] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [selectedPatient, setSelectedPatient] = useState(null); - const [isModalOpen, setIsModalOpen] = useState(false); + const [pacientes, setPacientes] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedPatient, setSelectedPatient] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); - // --- Lógica de Paginação INÍCIO --- - const [itemsPerPage, setItemsPerPage] = useState(5); - const [currentPage, setCurrentPage] = useState(1); + // --- Lógica de Paginação INÍCIO --- + const [itemsPerPage, setItemsPerPage] = useState(5); + const [currentPage, setCurrentPage] = useState(1); - const totalPages = Math.ceil(pacientes.length / itemsPerPage); + const totalPages = Math.ceil(pacientes.length / itemsPerPage); - const indexOfLastItem = currentPage * itemsPerPage; - const indexOfFirstItem = indexOfLastItem - itemsPerPage; - const currentItems = pacientes.slice(indexOfFirstItem, indexOfLastItem); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = pacientes.slice(indexOfFirstItem, indexOfLastItem); - const paginate = (pageNumber: number) => setCurrentPage(pageNumber); + const paginate = (pageNumber: number) => setCurrentPage(pageNumber); - // Funções de Navegação - const goToPrevPage = () => { - setCurrentPage((prev) => Math.max(1, prev - 1)); - }; + // Funções de Navegação + const goToPrevPage = () => { + setCurrentPage((prev) => Math.max(1, prev - 1)); + }; - const goToNextPage = () => { - setCurrentPage((prev) => Math.min(totalPages, prev + 1)); - }; - - // Lógica para gerar os números das páginas visíveis (máximo de 5) - const getVisiblePageNumbers = (totalPages: number, currentPage: number) => { - const pages: number[] = []; - const maxVisiblePages = 5; - const halfRange = Math.floor(maxVisiblePages / 2); - let startPage = Math.max(1, currentPage - halfRange); - let endPage = Math.min(totalPages, currentPage + halfRange); + const goToNextPage = () => { + setCurrentPage((prev) => Math.min(totalPages, prev + 1)); + }; - if (endPage - startPage + 1 < maxVisiblePages) { - if (endPage === totalPages) { - startPage = Math.max(1, totalPages - maxVisiblePages + 1); - } - if (startPage === 1) { - endPage = Math.min(totalPages, maxVisiblePages); - } - } - - for (let i = startPage; i <= endPage; i++) { - pages.push(i); - } - return pages; - }; - - const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage); + // Lógica para gerar os números das páginas visíveis (máximo de 5) + const getVisiblePageNumbers = (totalPages: number, currentPage: number) => { + const pages: number[] = []; + const maxVisiblePages = 5; + const halfRange = Math.floor(maxVisiblePages / 2); + let startPage = Math.max(1, currentPage - halfRange); + let endPage = Math.min(totalPages, currentPage + halfRange); - // Lógica para mudar itens por página, resetando para a página 1 - const handleItemsPerPageChange = (value: string) => { - setItemsPerPage(Number(value)); - setCurrentPage(1); - }; - // --- Lógica de Paginação FIM --- + if (endPage - startPage + 1 < maxVisiblePages) { + if (endPage === totalPages) { + startPage = Math.max(1, totalPages - maxVisiblePages + 1); + } + if (startPage === 1) { + endPage = Math.min(totalPages, maxVisiblePages); + } + } + + for (let i = startPage; i <= endPage; i++) { + pages.push(i); + } + return pages; + }; + + const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage); + + // Lógica para mudar itens por página, resetando para a página 1 + const handleItemsPerPageChange = (value: string) => { + setItemsPerPage(Number(value)); + setCurrentPage(1); + }; + // --- Lógica de Paginação FIM --- - const handleOpenModal = (patient: Paciente) => { - setSelectedPatient(patient); - setIsModalOpen(true); - }; + const handleOpenModal = (patient: Paciente) => { + setSelectedPatient(patient); + setIsModalOpen(true); + }; - const handleCloseModal = () => { - setSelectedPatient(null); - setIsModalOpen(false); - }; + const handleCloseModal = () => { + setSelectedPatient(null); + setIsModalOpen(false); + }; - const formatDate = (dateString: string | null | undefined) => { - if (!dateString) return "N/A"; - try { - const date = new Date(dateString); - return new Intl.DateTimeFormat("pt-BR").format(date); - } catch (e) { - return dateString; // Retorna o string original se o formato for inválido - } - }; + const formatDate = (dateString: string | null | undefined) => { + if (!dateString) return "N/A"; + try { + const date = new Date(dateString); + return new Intl.DateTimeFormat("pt-BR").format(date); + } catch (e) { + return dateString; // Retorna o string original se o formato for inválido + } + }; - const fetchPacientes = useCallback(async () => { - try { - setLoading(true); - setError(null); - const json = await api.get("/rest/v1/patients"); - const items = Array.isArray(json) - ? json - : Array.isArray(json?.data) - ? json.data - : []; + const fetchPacientes = useCallback(async () => { + try { + setLoading(true); + setError(null); + const json = await api.get("/rest/v1/patients"); + const items = Array.isArray(json) + ? json + : Array.isArray(json?.data) + ? json.data + : []; - const mapped: Paciente[] = items.map((p: any) => ({ - id: String(p.id ?? ""), - nome: p.full_name ?? "—", - telefone: p.phone_mobile ?? "N/A", - cidade: p.city ?? "N/A", - estado: p.state ?? "N/A", - ultimoAtendimento: formatDate(p.created_at), - proximoAtendimento: "N/A", // Necessita de lógica de agendamento real - email: p.email ?? "N/A", - birth_date: p.birth_date ?? "N/A", - cpf: p.cpf ?? "N/A", - blood_type: p.blood_type ?? "N/A", - weight_kg: p.weight_kg ?? 0, - height_m: p.height_m ?? 0, - street: p.street ?? "N/A", - number: p.number ?? "N/A", - complement: p.complement ?? "N/A", - neighborhood: p.neighborhood ?? "N/A", - cep: p.cep ?? "N/A", - })); + const mapped: Paciente[] = items.map((p: any) => ({ + id: String(p.id ?? ""), + nome: p.full_name ?? "—", + telefone: p.phone_mobile ?? "N/A", + cidade: p.city ?? "N/A", + estado: p.state ?? "N/A", + ultimoAtendimento: formatDate(p.created_at), + proximoAtendimento: "N/A", // Necessita de lógica de agendamento real + email: p.email ?? "N/A", + birth_date: p.birth_date ?? "N/A", + cpf: p.cpf ?? "N/A", + blood_type: p.blood_type ?? "N/A", + weight_kg: p.weight_kg ?? 0, + height_m: p.height_m ?? 0, + street: p.street ?? "N/A", + number: p.number ?? "N/A", + complement: p.complement ?? "N/A", + neighborhood: p.neighborhood ?? "N/A", + cep: p.cep ?? "N/A", + })); - setPacientes(mapped); - setCurrentPage(1); // Resetar a página ao carregar novos dados - } catch (e: any) { - console.error("Erro ao carregar pacientes:", e); - setError(e?.message || "Erro ao carregar pacientes"); - } finally { - setLoading(false); - } - }, []); + setPacientes(mapped); + setCurrentPage(1); // Resetar a página ao carregar novos dados + } catch (e: any) { + console.error("Erro ao carregar pacientes:", e); + setError(e?.message || "Erro ao carregar pacientes"); + } finally { + setLoading(false); + } + }, []); - useEffect(() => { - fetchPacientes(); - }, [fetchPacientes]); + useEffect(() => { + fetchPacientes(); + }, [fetchPacientes]); - return ( - -
- {/* Cabeçalho */} -
-
-

Pacientes

-

- Lista de pacientes vinculados -

-
- {/* Adicione um seletor de itens por página ao lado de um botão de 'Novo Paciente' se aplicável */} -
- - - - -
-
+ return ( + +
+ {/* Cabeçalho */} +
{/* Ajustado para flex-col em telas pequenas */} +
+

Pacientes

+

+ Lista de pacientes vinculados +

+
+ {/* Controles de filtro e novo paciente */} + {/* Alterado para que o Select e o Link ocupem a largura total em telas pequenas e fiquem lado a lado em telas maiores */} +
+ + + + +
+
-
-
- - - - - - - - - - - - - - {loading ? ( - - - - ) : error ? ( - - - - ) : pacientes.length === 0 ? ( - - - - ) : ( - currentItems.map((p) => ( - - - - - - - - - - )) - )} - -
Nome - Telefone - - Cidade - - Estado - - Último atendimento - - Próximo atendimento - Ações
- - Carregando pacientes... -
{`Erro: ${error}`}
- Nenhum paciente encontrado -
{p.nome} - {p.telefone} - - {p.cidade} - - {p.estado} - - {p.ultimoAtendimento} - - {p.proximoAtendimento} - - - - - - - handleOpenModal(p)}> - - Ver detalhes - - - - - Laudos - - - alert(`Agenda para paciente ID: ${p.id}`)}> - - Ver agenda - - { - // Simulação de exclusão (A exclusão real deve ser feita via API) - const newPacientes = pacientes.filter((pac) => pac.id !== p.id); - setPacientes(newPacientes); - alert(`Paciente ID: ${p.id} excluído`); - // Necessário chamar a API de exclusão aqui - }} - className="text-red-600 focus:bg-red-50 focus:text-red-600" +
+ {/* Tabela para Telas Médias e Grandes */} +
{/* Esconde em telas pequenas */} + + + + + + + + + + + + + + {loading ? ( + + + + ) : error ? ( + + + + ) : pacientes.length === 0 ? ( + + + + ) : ( + currentItems.map((p) => ( + + + + + + + + + + )) + )} + +
Nome + Telefone + + Cidade + + Estado + + Último atendimento + + Próximo atendimento + Ações
+ + Carregando pacientes... +
{`Erro: ${error}`}
+ Nenhum paciente encontrado +
{p.nome} + {p.telefone} + + {p.cidade} + + {p.estado} + + {p.ultimoAtendimento} + + {p.proximoAtendimento} + + + + + + + handleOpenModal(p)}> + + Ver detalhes + + + + + Laudos + + + alert(`Agenda para paciente ID: ${p.id}`)}> + + Ver agenda + + { + const newPacientes = pacientes.filter((pac) => pac.id !== p.id); + setPacientes(newPacientes); + alert(`Paciente ID: ${p.id} excluído`); + }} + className="text-red-600 focus:bg-red-50 focus:text-red-600" + > + + Excluir + + + +
+
+ + {/* Layout em Cards/Lista para Telas Pequenas */} +
{/* Visível apenas em telas pequenas */} + {loading ? ( +
+ + Carregando pacientes... +
+ ) : error ? ( +
{`Erro: ${error}`}
+ ) : pacientes.length === 0 ? ( +
+ Nenhum paciente encontrado +
+ ) : ( + currentItems.map((p) => ( +
+
{/* Adicionado padding à direita */} +
{/* Aumentado a fonte e break-words para evitar corte do nome */} + {p.nome || "—"} +
+ {/* Removido o 'truncate' e adicionado 'break-words' no telefone */} +
+ Telefone: **{p.telefone || "N/A"}** +
+
+
+ + + + + + handleOpenModal(p)}> + + Ver detalhes + + + + + Laudos + + + alert(`Agenda para paciente ID: ${p.id}`)}> + + Ver agenda + + { + const newPacientes = pacientes.filter((pac) => pac.id !== p.id); + setPacientes(newPacientes); + alert(`Paciente ID: ${p.id} excluído`); + }} + className="text-red-600 focus:bg-red-50 focus:text-red-600" + > + + Excluir + + + +
+
+ )) + )} +
+ + + {/* Paginação */} + {totalPages > 1 && ( +
+ + {/* Botão Anterior */} +
-
+ {"< Anterior"} + - {/* Paginação ATUALIZADA */} - {totalPages > 1 && ( -
- - {/* Botão Anterior */} - + {/* Números das Páginas */} + {visiblePageNumbers.map((number) => ( + + ))} - {/* Números das Páginas */} - {visiblePageNumbers.map((number) => ( - - ))} - - {/* Botão Próximo */} - - + {/* Botão Próximo */} + + +
+ )} +
- )} - {/* Fim da Paginação ATUALIZADA */} - -
-
-
+ ); } \ 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/login/page.tsx b/app/login/page.tsx index b193142..4be0708 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,82 +1,257 @@ // Caminho: app/login/page.tsx +"use client"; + +import { usersService } from "@/services/usersApi.mjs"; import { LoginForm } from "@/components/LoginForm"; import Link from "next/link"; import Image from "next/image"; import { Button } from "@/components/ui/button"; -import { ArrowLeft } from "lucide-react"; // Importa o ícone de seta +import { Input } from "@/components/ui/input"; +import { ArrowLeft, X } from "lucide-react"; +import { useState } from "react"; +import RenderFromTemplateContext from "next/dist/client/components/render-from-template-context"; export default function LoginPage() { - return ( -
- - {/* PAINEL ESQUERDO: O Formulário */} -
- - {/* Link para Voltar */} -
- - - Voltar à página inicial - -
+ const [isModalOpen, setIsModalOpen] = useState(false); + const [email, setEmail] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [message, setMessage] = useState<{ + type: "success" | "error"; + text: string; + } | null>(null); - {/* O contêiner principal que agora terá a sombra e o estilo de card */} -
-
-

Acesse sua conta

-

Bem-vindo(a) de volta ao MedConnect!

+ const handleOpenModal = () => { + // Tenta pegar o email do input do formulário de login + const emailInput = document.querySelector( + 'input[type="email"]' + ) as HTMLInputElement; + if (emailInput?.value) { + setEmail(emailInput.value); + } + setIsModalOpen(true); + }; + + const handleResetPassword = async () => { + if (!email.trim()) { + setMessage({ + type: "error", + text: "Por favor, insira um e-mail válido.", + }); + return; + } + + setIsLoading(true); + setMessage(null); + + try { + // Chama o método que já faz o fetch corretamente + const data = await usersService.resetPassword(email); + + console.log("Resposta resetPassword:", data); + + setMessage({ + type: "success", + text: "E-mail de recuperação enviado! Verifique sua caixa de entrada.", + }); + + setTimeout(() => { + setIsModalOpen(false); + setMessage(null); + setEmail(""); + }, 2000); + } catch (error) { + console.error("Erro no reset de senha:", error); + setMessage({ + type: "error", + text: + error instanceof Error + ? error.message + : "Erro ao enviar e-mail. Tente novamente.", + }); + } finally { + setIsLoading(false); + } + }; + + const closeModal = () => { + setIsModalOpen(false); + setMessage(null); + setEmail(""); + }; + + return ( + <> +
+ {/* PAINEL ESQUERDO: O Formulário */} +
+ {/* Link para Voltar */} +
+ + + Voltar à página inicial +
- - {/* Children para o LoginForm */} -
- - + {/* O contêiner principal que agora terá a sombra e o estilo de card */} +
+ {/* NOVO: Bloco da Logo e Nome (Painel Esquerdo) */} +
+ Logo MediConnect + + MedConnect + +
+ {/* FIM: Bloco da Logo e Nome */} + +
+ {/* Título de boas-vindas movido para baixo da logo */} +

+ Acesse sua conta +

+

+ Bem-vindo(a) de volta ao MedConnect! +

+
+ + + {/* Children para o LoginForm */} +
+ +
+
+ +
+ + Não tem uma conta de paciente?{" "} + + + + Crie uma agora
- +
+
-
- Não tem uma conta de paciente? - - - Crie uma agora - - + {/* PAINEL DIREITO: A Imagem e Branding */} +
+ {/* Usamos o componente para otimização e performance */} + Médica utilizando um tablet na clínica MedConnect + {/* Camada de sobreposição para escurecer a imagem e destacar o texto */} +
+ {/* BLOCO DE NOME ADICIONADO */} +
+

+ MedConnect +

+
+

+ Tecnologia e Cuidado a Serviço da Sua Saúde. +

+

+ Acesse seu portal para uma experiência de saúde integrada, segura + e eficiente. +

- {/* PAINEL DIREITO: A Imagem e Branding */} -
- {/* Usamos o componente para otimização e performance */} - Médica utilizando um tablet na clínica MedConnect - {/* Camada de sobreposição para escurecer a imagem e destacar o texto */} -
- {/* BLOCO DE NOME ADICIONADO */} -
-

- MedConnect -

-
-

- Tecnologia e Cuidado a Serviço da Sua Saúde. -

-

- Acesse seu portal para uma experiência de saúde integrada, segura e eficiente. -

-
-
+ {/* Modal de Recuperação de Senha */} + {isModalOpen && ( +
+
+ {/* Botão de fechar */} + -
+ {/* Cabeçalho */} +
+

+ Recuperar Senha +

+

+ Insira seu e-mail e enviaremos um link para redefinir sua senha. +

+
+ + {/* Input de e-mail */} +
+
+ + setEmail(e.target.value)} + placeholder="seu@email.com" + disabled={isLoading} + className="w-full" + /> +
+ + {/* Mensagem de feedback */} + {message && ( +
+ {message.text} +
+ )} + + {/* Botões */} +
+ + +
+
+
+
+ )} + ); -} \ No newline at end of file +} diff --git a/app/manager/dashboard/page.tsx b/app/manager/dashboard/page.tsx index df56541..6558bd0 100644 --- a/app/manager/dashboard/page.tsx +++ b/app/manager/dashboard/page.tsx @@ -1,6 +1,5 @@ "use client"; -import ManagerLayout from "@/components/manager-layout"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Calendar, Clock, Plus, User } from "lucide-react"; @@ -8,6 +7,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 +55,7 @@ export default function ManagerDashboard() { }, []); return ( - +
{/* Cabeçalho */}
@@ -185,6 +185,6 @@ export default function ManagerDashboard() {
-
+ ); } diff --git a/app/manager/home/[id]/editar/page.tsx b/app/manager/home/[id]/editar/page.tsx index 6619f67..89a5afb 100644 --- a/app/manager/home/[id]/editar/page.tsx +++ b/app/manager/home/[id]/editar/page.tsx @@ -10,7 +10,7 @@ import { Textarea } from "@/components/ui/textarea" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Checkbox } from "@/components/ui/checkbox" import { Save, Loader2, ArrowLeft } from "lucide-react" -import ManagerLayout from "@/components/manager-layout" +import Sidebar from "@/components/Sidebar" import { doctorsService } from "services/doctorsApi.mjs"; const UF_LIST = ["AC", "AL", "AP", "AM", "BA", "CE", "DF", "ES", "GO", "MA", "MT", "MS", "MG", "PA", "PB", "PR", "PE", "PI", "RJ", "RN", "RS", "RO", "RR", "SC", "SP", "SE", "TO"]; @@ -207,17 +207,17 @@ export default function EditarMedicoPage() { }; if (loading) { return ( - +

Carregando dados do médico...

-
+ ); } return ( - +
@@ -487,6 +487,6 @@ export default function EditarMedicoPage() {
- + ); } \ No newline at end of file diff --git a/app/manager/home/page.tsx b/app/manager/home/page.tsx index 03b64a5..afa89ed 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 { @@ -33,7 +24,6 @@ interface Doctor { status?: string; } - interface DoctorDetails { nome: string; crm: string; @@ -41,11 +31,11 @@ interface DoctorDetails { contato: { celular?: string; telefone1?: string; - } + }; endereco: { cidade?: string; estado?: string; - } + }; convenio?: string; vip?: boolean; status?: string; @@ -80,7 +70,7 @@ export default function DoctorsPage() { const data: Doctor[] = await doctorsService.list(); const dataWithStatus = data.map((doc, index) => ({ ...doc, - status: index % 3 === 0 ? "Inativo" : index % 2 === 0 ? "Férias" : "Ativo" + status: index % 3 === 0 ? "Inativo" : index % 2 === 0 ? "Férias" : "Ativo", })); setDoctors(dataWithStatus || []); setCurrentPage(1); @@ -93,12 +83,10 @@ export default function DoctorsPage() { } }, []); - useEffect(() => { fetchDoctors(); }, [fetchDoctors]); - const openDetailsDialog = async (doctor: Doctor) => { setDetailsDialogOpen(true); setDoctorDetails({ @@ -115,7 +103,6 @@ export default function DoctorsPage() { }); }; - const handleDelete = async () => { if (doctorToDeleteId === null) return; setLoading(true); @@ -138,11 +125,11 @@ export default function DoctorsPage() { }; const uniqueSpecialties = useMemo(() => { - const specialties = doctors.map(doctor => doctor.specialty).filter(Boolean); + const specialties = doctors.map((doctor) => doctor.specialty).filter(Boolean); return [...new Set(specialties)]; }, [doctors]); - const filteredDoctors = doctors.filter(doctor => { + const filteredDoctors = doctors.filter((doctor) => { const specialtyMatch = specialtyFilter === "all" || doctor.specialty === specialtyFilter; const statusMatch = statusFilter === "all" || doctor.status === statusFilter; return specialtyMatch && statusMatch; @@ -191,11 +178,9 @@ export default function DoctorsPage() { setCurrentPage(1); }; - return ( - +
- {/* Cabeçalho */}
@@ -204,29 +189,26 @@ export default function DoctorsPage() {
- {/* Filtros e Itens por Página */}
- - Especialidade - + Especialidade
- - Status - + Status
- - Itens por página - - @@ -262,9 +240,8 @@ export default function DoctorsPage() {
- - {/* Tabela de Médicos */} -
+ {/* Tabela de Médicos (Visível em Telas Médias e Maiores) */} +
{loading ? (
@@ -287,8 +264,8 @@ export default function DoctorsPage() { Nome CRM Especialidade - Status - Cidade/Estado + Status + Cidade/Estado Ações @@ -305,7 +282,6 @@ export default function DoctorsPage() { : "N/A"} - {/* ===== INÍCIO DA ALTERAÇÃO ===== */}
Ações
@@ -331,7 +307,6 @@ export default function DoctorsPage() {
- {/* ===== FIM DA ALTERAÇÃO ===== */} ))} @@ -341,6 +316,61 @@ export default function DoctorsPage() { )}
+ {/* Cards de Médicos (Visível Apenas em Telas Pequenas) */} +
+ {loading ? ( +
+ + Carregando médicos... +
+ ) : error ? ( +
{error}
+ ) : filteredDoctors.length === 0 ? ( +
+ {doctors.length === 0 + ? <>Nenhum médico cadastrado. Adicione um novo. + : "Nenhum médico encontrado com os filtros aplicados." + } +
+ ) : ( +
+ {currentItems.map((doctor) => ( +
+
+
{doctor.full_name}
+
{doctor.specialty}
+
+ + +
Ações
+
+ + openDetailsDialog(doctor)}> + + Ver detalhes + + + + + Editar + + + + + Marcar consulta + + openDeleteDialog(doctor.id)}> + + Excluir + + +
+
+ ))} +
+ )} +
+ {/* Paginação */} {totalPages > 1 && (
@@ -356,10 +386,11 @@ export default function DoctorsPage() { @@ -380,9 +411,7 @@ export default function DoctorsPage() { Confirma a exclusão? - - Esta ação é irreversível e excluirá permanentemente o registro deste médico. - + Esta ação é irreversível e excluirá permanentemente o registro deste médico. Cancelar @@ -403,25 +432,41 @@ export default function DoctorsPage() {

Informações Principais

-
CRM: {doctorDetails.crm}
-
Especialidade: {doctorDetails.especialidade}
-
Celular: {doctorDetails.contato.celular || 'N/A'}
-
Localização: {`${doctorDetails.endereco.cidade || 'N/A'}/${doctorDetails.endereco.estado || 'N/A'}`}
+
+ CRM: {doctorDetails.crm} +
+
+ Especialidade: {doctorDetails.especialidade} +
+
+ Celular: {doctorDetails.contato.celular || "N/A"} +
+
+ Localização: {`${doctorDetails.endereco.cidade || "N/A"}/${doctorDetails.endereco.estado || "N/A"}`} +

Atendimento e Convênio

-
Convênio: {doctorDetails.convenio || 'N/A'}
-
VIP: {doctorDetails.vip ? "Sim" : "Não"}
-
Status: {doctorDetails.status || 'N/A'}
-
Último atendimento: {doctorDetails.ultimo_atendimento || 'N/A'}
-
Próximo atendimento: {doctorDetails.proximo_atendimento || 'N/A'}
+
+ Convênio: {doctorDetails.convenio || "N/A"} +
+
+ VIP: {doctorDetails.vip ? "Sim" : "Não"} +
+
+ Status: {doctorDetails.status || "N/A"} +
+
+ Último atendimento: {doctorDetails.ultimo_atendimento || "N/A"} +
+
+ Próximo atendimento: {doctorDetails.proximo_atendimento || "N/A"} +
)} - {doctorDetails === null && !loading && ( -
Detalhes não disponíveis.
- )} + {doctorDetails === null && !loading &&
Detalhes não disponíveis.
} @@ -430,6 +475,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..55b85cf 100644 --- a/app/manager/pacientes/page.tsx +++ b/app/manager/pacientes/page.tsx @@ -1,4 +1,3 @@ - "use client"; import { useState, useEffect, useCallback } from "react"; @@ -6,10 +5,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 +144,7 @@ export default function PacientesPage() { }; return ( - +
{/* Header (Responsividade OK) */}
@@ -156,6 +155,7 @@ export default function PacientesPage() {
{/* Bloco de Filtros (Responsividade APLICADA) */} + {/* Adicionado flex-wrap para permitir que os itens quebrem para a linha de baixo */}
@@ -165,14 +165,15 @@ export default function PacientesPage() { placeholder="Buscar por nome ou telefone..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} - className="w-full sm:flex-grow sm:min-w-[150px] p-2 border rounded-md text-sm" + // w-full no mobile, depois flex-grow para ocupar o espaço disponível + className="w-full sm:flex-grow sm:max-w-[300px] p-2 border rounded-md text-sm" /> - {/* Convênio - Ocupa metade da linha no mobile */} -
+ {/* Convênio - Ocupa a largura total em telas pequenas, depois se ajusta */} +
Convênio
- {/* VIP - Ocupa a outra metade da linha no mobile */} -
+ {/* VIP - Ocupa a largura total em telas pequenas, depois se ajusta */} +
VIP
- {/* Aniversariantes - Vai para a linha de baixo no mobile, ocupando 100% */} + {/* Aniversariantes - Ocupa 100% no mobile, e se alinha à direita no md+ */}
- {/* Tabela (Responsividade APLICADA) */} -
-
+ {/* --- SEÇÃO DE TABELA (VISÍVEL EM TELAS MAIORES OU IGUAIS A MD) --- */} + {/* Garantir que a tabela se esconda em telas menores e apareça em MD+ */} +
+
{/* Permite rolagem horizontal se a tabela for muito larga */} {error ? (
{`Erro ao carregar pacientes: ${error}`}
) : loading ? ( @@ -217,18 +219,14 @@ export default function PacientesPage() { Carregando pacientes...
) : ( - // min-w ajustado para responsividade - +
{/* min-w para evitar que a tabela se contraia demais */} - {/* Coluna oculta em telas muito pequenas */} + {/* Ajustes de visibilidade de colunas para diferentes breakpoints */} - {/* Coluna oculta em telas pequenas e muito pequenas */} - {/* Coluna oculta em telas muito pequenas */} - {/* Colunas ocultas em telas médias, pequenas e muito pequenas */} @@ -257,7 +255,6 @@ export default function PacientesPage() { - {/* Aplicação das classes de visibilidade */} @@ -300,53 +297,109 @@ export default function PacientesPage() {
NomeTelefoneCidade / EstadoConvênioÚltimo atendimento Próximo atendimento Ações {patient.telefone} {`${patient.cidade} / ${patient.estado}`} {patient.convenio}
)}
+
- {/* Paginação */} - {totalPages > 1 && !loading && ( -
- {/* Renderização dos botões de número de página (Limitando a 5) */} -
{/* Increased space-x for more separation */} - {/* Botão Anterior */} - + {/* --- SEÇÃO DE CARDS (VISÍVEL APENAS EM TELAS MENORES QUE MD) --- */} + {/* Garantir que os cards apareçam em telas menores e se escondam em MD+ */} +
+ {error ? ( +
{`Erro ao carregar pacientes: ${error}`}
+ ) : loading ? ( +
+ Carregando pacientes... +
+ ) : filteredPatients.length === 0 ? ( +
+ {allPatients.length === 0 ? "Nenhum paciente cadastrado" : "Nenhum paciente encontrado com os filtros aplicados"} +
+ ) : ( +
+ {currentPatients.map((patient) => ( +
+
+
+ {patient.nome} + {patient.vip && ( + VIP + )} +
+
Telefone: {patient.telefone}
+
Convênio: {patient.convenio}
+
+ + +
+
+ + openDetailsDialog(String(patient.id))}> + + Ver detalhes + - {Array.from({ length: totalPages }, (_, index) => index + 1) - .slice(Math.max(0, page - 3), Math.min(totalPages, page + 2)) - .map((pageNumber) => ( - - ))} + + + + Editar + + - {/* Botão Próximo */} - -
+ + + Marcar consulta + + openDeleteDialog(String(patient.id))}> + + Excluir + + + +
+ ))}
)}
+ {/* Paginação */} + {totalPages > 1 && !loading && ( +
+
{/* Adicionado flex-wrap e justify-center para botões da paginação */} + + + {Array.from({ length: totalPages }, (_, index) => index + 1) + .slice(Math.max(0, page - 3), Math.min(totalPages, page + 2)) + .map((pageNumber) => ( + + ))} + + +
+
+ )} + {/* AlertDialogs (Permanecem os mesmos) */} - {/* ... (AlertDialog de Exclusão) ... */} Confirmar exclusão @@ -362,7 +415,6 @@ export default function PacientesPage() { - {/* ... (AlertDialog de Detalhes) ... */} Detalhes do Paciente @@ -376,7 +428,7 @@ export default function PacientesPage() {
{patientDetails.error}
) : (
-
+

Nome Completo

{patientDetails.full_name}

@@ -412,7 +464,7 @@ export default function PacientesPage() {

Endereço

-
+

Rua

{`${patientDetails.street}, ${patientDetails.number}`}

@@ -449,6 +501,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..4166d53 100644 --- a/app/manager/usuario/novo/page.tsx +++ b/app/manager/usuario/novo/page.tsx @@ -1,4 +1,4 @@ -// /app/manager/usuario/novo/page.tsx +// ARQUIVO COMPLETO PARA: app/manager/usuario/novo/page.tsx "use client"; @@ -9,11 +9,12 @@ 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 { Save, Loader2, Pause } from "lucide-react"; -import ManagerLayout from "@/components/manager-layout"; +import { Save, Loader2 } from "lucide-react"; import { usersService } from "@/services/usersApi.mjs"; -import { doctorsService } from "@/services/doctorsApi.mjs"; // Importação adicionada +import { doctorsService } from "@/services/doctorsApi.mjs"; import { login } from "services/api.mjs"; +import { isValidCPF } from "@/lib/utils"; // 1. IMPORTAÇÃO DA FUNÇÃO DE VALIDAÇÃO +import Sidebar from "@/components/Sidebar"; interface UserFormData { email: string; @@ -23,7 +24,6 @@ interface UserFormData { senha: string; confirmarSenha: string; cpf: string; - // Novos campos para Médico crm: string; crm_uf: string; specialty: string; @@ -37,7 +37,6 @@ const defaultFormData: UserFormData = { senha: "", confirmarSenha: "", cpf: "", - // Valores iniciais para campos de Médico crm: "", crm_uf: "", specialty: "", @@ -62,7 +61,6 @@ export default function NovoUsuarioPage() { if (key === "telefone") { updatedValue = formatPhone(value); } else if (key === "crm_uf") { - // Converte UF para maiúsculas updatedValue = value.toUpperCase(); } setFormData((prev) => ({ ...prev, [key]: updatedValue })); @@ -72,7 +70,7 @@ export default function NovoUsuarioPage() { e.preventDefault(); setError(null); - if (!formData.email || !formData.nomeCompleto || !formData.papel || !formData.senha || !formData.confirmarSenha) { + if (!formData.email || !formData.nomeCompleto || !formData.papel || !formData.senha || !formData.confirmarSenha || !formData.cpf) { setError("Por favor, preencha todos os campos obrigatórios."); return; } @@ -82,7 +80,12 @@ export default function NovoUsuarioPage() { return; } - // Validação adicional para Médico + // 2. VALIDAÇÃO DO CPF ANTES DO ENVIO + if (!isValidCPF(formData.cpf)) { + setError("O CPF informado é inválido. Por favor, verifique os dígitos."); + return; + } + if (formData.papel === "medico") { if (!formData.crm || !formData.crm_uf) { setError("Para a função 'Médico', o CRM e a UF do CRM são obrigatórios."); @@ -94,7 +97,6 @@ export default function NovoUsuarioPage() { try { if (formData.papel === "medico") { - // Lógica para criação de Médico const doctorPayload = { email: formData.email.trim().toLowerCase(), full_name: formData.nomeCompleto, @@ -102,41 +104,29 @@ export default function NovoUsuarioPage() { crm: formData.crm, crm_uf: formData.crm_uf, specialty: formData.specialty || null, - phone_mobile: formData.telefone || null, // Usando phone_mobile conforme o schema + phone_mobile: formData.telefone || null, }; - - console.log("📤 Enviando payload para Médico:"); - console.log(doctorPayload); - - // Chamada ao endpoint específico para criação de médico await doctorsService.create(doctorPayload); - } else { - // Lógica para criação de Outras Roles const isPatient = formData.papel === "paciente"; - const userPayload = { email: formData.email.trim().toLowerCase(), password: formData.senha, full_name: formData.nomeCompleto, phone: formData.telefone || null, - role: formData.papel, + roles: [formData.papel, "paciente"], cpf: formData.cpf, - create_patient_record: isPatient, // true se a role for 'paciente' - phone_mobile: isPatient ? formData.telefone || null : undefined, // Enviar phone_mobile se for paciente + create_patient_record: isPatient, + phone_mobile: isPatient ? formData.telefone || null : undefined, }; - - console.log("📤 Enviando payload para Usuário Comum:"); - console.log(userPayload); - - // Chamada ao endpoint padrão para criação de usuário await usersService.create_user(userPayload); } - router.push("/manager/usuario"); } catch (e: any) { console.error("Erro ao criar usuário:", e); - setError(e?.message || "Não foi possível criar o usuário. Verifique os dados e tente novamente."); + // 3. MENSAGEM DE ERRO MELHORADA + const detail = e.message?.split('detail:"')[1]?.split('"')[0] || e.message; + setError(detail.replace(/\\/g, "") || "Não foi possível criar o usuário. Verifique os dados e tente novamente."); } finally { setIsSaving(false); } @@ -145,7 +135,7 @@ export default function NovoUsuarioPage() { const isMedico = formData.papel === "medico"; return ( - +
@@ -193,26 +183,22 @@ export default function NovoUsuarioPage() {
- {/* Campos Condicionais para Médico */} {isMedico && ( <>
handleInputChange("crm", e.target.value)} placeholder="Número do CRM" required />
-
handleInputChange("crm_uf", e.target.value)} placeholder="Ex: SP" maxLength={2} required />
-
handleInputChange("specialty", e.target.value)} placeholder="Ex: Cardiologia" />
)} - {/* Fim dos Campos Condicionais */}
@@ -233,7 +219,7 @@ export default function NovoUsuarioPage() {
- handleInputChange("cpf", e.target.value)} placeholder="xxx.xxx.xxx-xx" required /> + handleInputChange("cpf", e.target.value)} placeholder="Apenas números" required />
@@ -250,6 +236,6 @@ export default function NovoUsuarioPage() {
- + ); } diff --git a/app/manager/usuario/page.tsx b/app/manager/usuario/page.tsx index 805fb0c..9cc8bbc 100644 --- a/app/manager/usuario/page.tsx +++ b/app/manager/usuario/page.tsx @@ -1,29 +1,14 @@ -// app/manager/usuario/page.tsx "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; @@ -49,17 +34,15 @@ export default function UsersPage() { const [userDetails, setUserDetails] = useState( null ); - // Ajuste 1: Definir 'all' como valor inicial para garantir que todos os usuários sejam exibidos por padrão. const [selectedRole, setSelectedRole] = useState("all"); // --- Lógica de Paginação INÍCIO --- const [itemsPerPage, setItemsPerPage] = useState(10); const [currentPage, setCurrentPage] = useState(1); - // Lógica para mudar itens por página, resetando para a página 1 const handleItemsPerPageChange = (value: string) => { setItemsPerPage(Number(value)); - setCurrentPage(1); // Resetar para a primeira página + setCurrentPage(1); }; // --- Lógica de Paginação FIM --- @@ -95,8 +78,7 @@ export default function UsersPage() { }); setUsers(mapped); - setCurrentPage(1); // Resetar a página após carregar - console.log("[fetchUsers] mapped count:", mapped.length); + setCurrentPage(1); } catch (err: any) { console.error("Erro ao buscar usuários:", err); setError("Não foi possível carregar os usuários. Veja console."); @@ -123,9 +105,7 @@ export default function UsersPage() { setUserDetails(null); try { - console.log("[openDetailsDialog] user_id:", flatUser.user_id); const data = await usersService.full_data(flatUser.user_id); - console.log("[openDetailsDialog] full_data returned:", data); setUserDetails(data); } catch (err: any) { console.error("Erro ao carregar detalhes:", err); @@ -138,23 +118,19 @@ export default function UsersPage() { } }; - // 1. Filtragem const filteredUsers = selectedRole && selectedRole !== "all" ? users.filter((u) => u.role === selectedRole) : users; - // 2. Paginação (aplicada sobre a lista filtrada) const indexOfLastItem = currentPage * itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage; const currentItems = filteredUsers.slice(indexOfFirstItem, indexOfLastItem); - // Função para mudar de página const paginate = (pageNumber: number) => setCurrentPage(pageNumber); const totalPages = Math.ceil(filteredUsers.length / itemsPerPage); - // --- Funções e Lógica de Navegação ADICIONADAS --- const goToPrevPage = () => { setCurrentPage((prev) => Math.max(1, prev - 1)); }; @@ -163,15 +139,13 @@ export default function UsersPage() { setCurrentPage((prev) => Math.min(totalPages, prev + 1)); }; - // Lógica para gerar os números das páginas visíveis const getVisiblePageNumbers = (totalPages: number, currentPage: number) => { const pages: number[] = []; - const maxVisiblePages = 5; // Número máximo de botões de página a serem exibidos (ex: 2, 3, 4, 5, 6) + const maxVisiblePages = 5; const halfRange = Math.floor(maxVisiblePages / 2); let startPage = Math.max(1, currentPage - halfRange); let endPage = Math.min(totalPages, currentPage + halfRange); - // Ajusta para manter o número fixo de botões quando nos limites if (endPage - startPage + 1 < maxVisiblePages) { if (endPage === totalPages) { startPage = Math.max(1, totalPages - maxVisiblePages + 1); @@ -188,11 +162,9 @@ export default function UsersPage() { }; const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage); - // --- Fim das Funções e Lógica de Navegação ADICIONADAS --- - return ( - +
{/* Header */} @@ -213,17 +185,17 @@ export default function UsersPage() { {/* Select de Filtro por Papel - Ajustado para resetar a página */}
- + Filtrar por papel - + {/* w-full para mobile, w-[140px] para sm+ */} @@ -263,7 +235,7 @@ export default function UsersPage() {
{/* Fim do Filtro e Itens por Página */} - {/* Tabela */} + {/* Tabela/Lista */}
{loading ? (
@@ -278,10 +250,10 @@ export default function UsersPage() {
) : ( <> - - + {/* Tabela para Telas Médias e Grandes */} +
+ - @@ -290,15 +262,8 @@ export default function UsersPage() { - {/* Usando currentItems para a paginação */} {currentItems.map((u) => ( - - + @@ -326,7 +291,33 @@ export default function UsersPage() {
ID Nome E-mail Telefone
- {u.id} -
{u.full_name}
- {/* Paginação ATUALIZADA */} + {/* Layout em Cards/Lista para Telas Pequenas */} +
+ {currentItems.map((u) => ( +
+
+
+ {u.full_name || "—"} +
+
+ {u.role || "—"} +
+
+
+ +
+
+ ))} +
+ + {/* Paginação */} {totalPages > 1 && (
@@ -364,7 +355,6 @@ export default function UsersPage() {
)} - {/* Fim da Paginação ATUALIZADA */} )}
@@ -401,7 +391,6 @@ export default function UsersPage() { Roles:{" "} {userDetails.roles?.join(", ")}
- {/* Melhoria na visualização das permissões no modal */}
Permissões:
    @@ -424,6 +413,6 @@ export default function UsersPage() {
-
+ ); } \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index e65d61a..fcefd3c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,112 +1,184 @@ "use client"; -import Link from "next/link" -import { Button } from "@/components/ui/button" - +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { useState } from "react"; export default function InicialPage() { + const [isMenuOpen, setIsMenuOpen] = useState(false); + return (
- {} -
- Horário: 08h00 - 21h00 - Email: contato@mediconnect.com + {/* Barra superior de informações */} +
+ Horário: 08h00 - 21h00 + Email: contato@mediconnect.com
- {} -
-

MediConnect

-