From 8a63219cf6324d30677fd17efa3e918b9fc68e46 Mon Sep 17 00:00:00 2001 From: Jhony Date: Wed, 26 Nov 2025 22:28:00 -0300 Subject: [PATCH 1/2] J.R. --- app/doctor/medicos/[id]/laudos/novo/page.tsx | 4 - app/doctor/medicos/page.tsx | 42 +- app/manager/home/page.tsx | 14 +- app/manager/pacientes/page.tsx | 208 +++++----- app/patient/reports/page.tsx | 398 ++++--------------- app/secretary/pacientes/page.tsx | 207 +++++----- services/reportsApi.mjs | 2 +- 7 files changed, 318 insertions(+), 557 deletions(-) diff --git a/app/doctor/medicos/[id]/laudos/novo/page.tsx b/app/doctor/medicos/[id]/laudos/novo/page.tsx index 9c9b8a9..0fa6b8d 100644 --- a/app/doctor/medicos/[id]/laudos/novo/page.tsx +++ b/app/doctor/medicos/[id]/laudos/novo/page.tsx @@ -106,10 +106,6 @@ export default function NovoLaudoPage() {
-
- - -
diff --git a/app/doctor/medicos/page.tsx b/app/doctor/medicos/page.tsx index 4dd6c87..1416c8e 100644 --- a/app/doctor/medicos/page.tsx +++ b/app/doctor/medicos/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useState, useCallback } from "react"; import Link from "next/link"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; -import { Eye, Edit, Calendar, Trash2, Loader2 } from "lucide-react"; +import { Eye, Edit, Calendar, Trash2, Loader2, MoreVertical } 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"; @@ -185,7 +185,7 @@ export default function PacientesPage() { 20 por página - + @@ -262,9 +262,10 @@ export default function PacientesPage() { - + handleOpenModal(p)}> @@ -272,16 +273,16 @@ export default function PacientesPage() { Ver detalhes - + Laudos - alert(`Agenda para paciente ID: ${p.id}`)}> + {/* alert(`Agenda para paciente ID: ${p.id}`)}> Ver agenda - - */} + {/* { const newPacientes = pacientes.filter((pac) => pac.id !== p.id); setPacientes(newPacientes); @@ -291,7 +292,7 @@ export default function PacientesPage() { > Excluir - + */} @@ -323,7 +324,7 @@ export default function PacientesPage() { {p.nome || "—"}
{/* Removido o 'truncate' e adicionado 'break-words' no telefone */} -
+
Telefone: **{p.telefone || "N/A"}**
@@ -387,11 +388,10 @@ export default function PacientesPage() { @@ -411,11 +411,11 @@ export default function PacientesPage() {
- - - ); + + + ); } \ No newline at end of file diff --git a/app/manager/home/page.tsx b/app/manager/home/page.tsx index afa89ed..c957c57 100644 --- a/app/manager/home/page.tsx +++ b/app/manager/home/page.tsx @@ -6,7 +6,7 @@ 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 { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react" +import { Edit, Trash2, Eye, Calendar, Filter, Loader2, MoreVertical } from "lucide-react" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog" import { doctorsService } from "services/doctorsApi.mjs"; @@ -284,7 +284,10 @@ export default function DoctorsPage() { -
Ações
+
openDetailsDialog(doctor)}> @@ -342,7 +345,7 @@ export default function DoctorsPage() { -
Ações
+
A99ções
openDetailsDialog(doctor)}> @@ -386,11 +389,10 @@ export default function DoctorsPage() { diff --git a/app/manager/pacientes/page.tsx b/app/manager/pacientes/page.tsx index 55b85cf..61487bc 100644 --- a/app/manager/pacientes/page.tsx +++ b/app/manager/pacientes/page.tsx @@ -5,44 +5,44 @@ 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 { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react"; +import { Edit, Trash2, Eye, Calendar, Filter, Loader2, MoreVertical } from "lucide-react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { patientsService } from "@/services/patientsApi.mjs"; import Sidebar from "@/components/Sidebar"; // Defina o tamanho da página. -const PAGE_SIZE = 5; +const PAGE_SIZE = 5; export default function PacientesPage() { // --- ESTADOS DE DADOS E GERAL --- const [searchTerm, setSearchTerm] = useState(""); const [convenioFilter, setConvenioFilter] = useState("all"); const [vipFilter, setVipFilter] = useState("all"); - - // Lista completa, carregada da API uma única vez - const [allPatients, setAllPatients] = useState([]); - // Lista após a aplicação dos filtros (base para a paginação) - const [filteredPatients, setFilteredPatients] = useState([]); - const [loading, setLoading] = useState(true); + // Lista completa, carregada da API uma única vez + const [allPatients, setAllPatients] = useState([]); + // Lista após a aplicação dos filtros (base para a paginação) + const [filteredPatients, setFilteredPatients] = useState([]); + + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - + // --- ESTADOS DE PAGINAÇÃO --- const [page, setPage] = useState(1); - + // CÁLCULO DA PAGINAÇÃO const totalPages = Math.ceil(filteredPatients.length / PAGE_SIZE); const startIndex = (page - 1) * PAGE_SIZE; const endIndex = startIndex + PAGE_SIZE; // Pacientes a serem exibidos na tabela (aplicando a paginação) - const currentPatients = filteredPatients.slice(startIndex, endIndex); + const currentPatients = filteredPatients.slice(startIndex, endIndex); // --- ESTADOS DE DIALOGS --- const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [patientToDelete, setPatientToDelete] = useState(null); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); const [patientDetails, setPatientDetails] = useState(null); - + // --- FUNÇÕES DE LÓGICA --- // 1. Função para carregar TODOS os pacientes da API @@ -53,7 +53,7 @@ export default function PacientesPage() { try { // Como o backend retorna um array, chamamos sem paginação const res = await patientsService.list(); - + const mapped = res.map((p: any) => ({ id: String(p.id ?? ""), nome: p.full_name ?? "—", @@ -61,7 +61,7 @@ export default function PacientesPage() { cidade: p.city ?? "—", estado: p.state ?? "—", // Formate as datas se necessário, aqui usamos como string - ultimoAtendimento: p.last_visit_at?.split('T')[0] ?? "—", + ultimoAtendimento: p.last_visit_at?.split('T')[0] ?? "—", proximoAtendimento: p.next_appointment_at?.split('T')[0] ?? "—", vip: Boolean(p.vip ?? false), convenio: p.convenio ?? "Particular", // Define um valor padrão @@ -83,27 +83,27 @@ export default function PacientesPage() { useEffect(() => { const filtered = allPatients.filter((patient) => { // Filtro por termo de busca (Nome ou Telefone) - const matchesSearch = - patient.nome?.toLowerCase().includes(searchTerm.toLowerCase()) || + const matchesSearch = + patient.nome?.toLowerCase().includes(searchTerm.toLowerCase()) || patient.telefone?.includes(searchTerm); - + // Filtro por Convênio - const matchesConvenio = - convenioFilter === "all" || + const matchesConvenio = + convenioFilter === "all" || patient.convenio === convenioFilter; - + // Filtro por VIP - const matchesVip = - vipFilter === "all" || - (vipFilter === "vip" && patient.vip) || + const matchesVip = + vipFilter === "all" || + (vipFilter === "vip" && patient.vip) || (vipFilter === "regular" && !patient.vip); return matchesSearch && matchesConvenio && matchesVip; }); - + setFilteredPatients(filtered); // Garante que a página atual seja válida após a filtragem - setPage(1); + setPage(1); }, [allPatients, searchTerm, convenioFilter, vipFilter]); // 3. Efeito inicial para buscar os pacientes @@ -114,7 +114,7 @@ export default function PacientesPage() { // --- LÓGICA DE AÇÕES (DELETAR / VER DETALHES) --- - + const openDetailsDialog = async (patientId: string) => { setDetailsDialogOpen(true); setPatientDetails(null); @@ -158,7 +158,7 @@ export default function PacientesPage() { {/* Adicionado flex-wrap para permitir que os itens quebrem para a linha de baixo */}
- + {/* Busca - Ocupa 100% no mobile, depois cresce */} setSearchTerm(e.target.value)} // 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" + className="w-full sm:flex-grow sm:max-w-[300px] p-2 border rounded-md text-sm" /> {/* Convênio - Ocupa a largura total em telas pequenas, depois se ajusta */} @@ -200,7 +200,7 @@ export default function PacientesPage() {
- + {/* Aniversariantes - Ocupa 100% no mobile, e se alinha à direita no md+ */} openDetailsDialog(String(patient.id))}> @@ -301,7 +304,7 @@ export default function PacientesPage() { {/* --- 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 ? ( @@ -321,44 +324,47 @@ export default function PacientesPage() { {patient.nome} {patient.vip && ( VIP - )} + )}
Telefone: {patient.telefone}
Convênio: {patient.convenio}
- - -
-
- - openDetailsDialog(String(patient.id))}> - - Ver detalhes - + + + + + + openDetailsDialog(String(patient.id))}> + + Ver detalhes + - - - - Editar - - + + + + Editar + + - - - Marcar consulta - - openDeleteDialog(String(patient.id))}> - - Excluir - - - + + + Marcar consulta + + openDeleteDialog(String(patient.id))}> + + Excluir + + +
))} )} - + {/* Paginação */} {totalPages > 1 && !loading && (
@@ -397,7 +403,7 @@ export default function PacientesPage() {
)} - + {/* AlertDialogs (Permanecem os mesmos) */} @@ -430,65 +436,65 @@ export default function PacientesPage() {
-

Nome Completo

-

{patientDetails.full_name}

+

Nome Completo

+

{patientDetails.full_name}

-

Email

-

{patientDetails.email}

+

Email

+

{patientDetails.email}

-

Telefone

-

{patientDetails.phone_mobile}

+

Telefone

+

{patientDetails.phone_mobile}

-

Data de Nascimento

-

{patientDetails.birth_date}

+

Data de Nascimento

+

{patientDetails.birth_date}

-

CPF

-

{patientDetails.cpf}

+

CPF

+

{patientDetails.cpf}

-

Tipo Sanguíneo

-

{patientDetails.blood_type}

+

Tipo Sanguíneo

+

{patientDetails.blood_type}

-

Peso (kg)

-

{patientDetails.weight_kg}

+

Peso (kg)

+

{patientDetails.weight_kg}

-

Altura (m)

-

{patientDetails.height_m}

+

Altura (m)

+

{patientDetails.height_m}

Endereço

-
-

Rua

-

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

-
-
-

Complemento

-

{patientDetails.complement}

-
-
-

Bairro

-

{patientDetails.neighborhood}

-
-
-

Cidade

-

{patientDetails.cidade}

-
-
-

Estado

-

{patientDetails.estado}

-
-
-

CEP

-

{patientDetails.cep}

-
+
+

Rua

+

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

+
+
+

Complemento

+

{patientDetails.complement}

+
+
+

Bairro

+

{patientDetails.neighborhood}

+
+
+

Cidade

+

{patientDetails.cidade}

+
+
+

Estado

+

{patientDetails.estado}

+
+
+

CEP

+

{patientDetails.cep}

+
diff --git a/app/patient/reports/page.tsx b/app/patient/reports/page.tsx index d1fa91c..32ec3c4 100644 --- a/app/patient/reports/page.tsx +++ b/app/patient/reports/page.tsx @@ -1,159 +1,67 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, useMemo } from "react" 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 { FileText, Download, Eye, Calendar, User, X, Loader2 } from "lucide-react" import Sidebar from "@/components/Sidebar" +import { useAuthLayout } from "@/hooks/useAuthLayout" +import { reportsApi } from "@/services/reportsApi.mjs" interface Report { - id: string - title: string - doctor: string - date: string - type: string - status: "disponivel" | "pendente" - description: string - content: { - patientInfo: { - name: string - age: number - gender: string - id: string - } - examDetails: { - requestingDoctor: string - examDate: string - reportDate: string - technique: string - } - findings: string - conclusion: string - recommendations?: string - } + id: string; + order_number: string; + patient_id: string; + status: string; + exam: string; + requested_by: string; + cid_code: string; + diagnosis: string; + conclusion: string; + content_html: string; + content_json: any; + hide_date: boolean; + hide_signature: boolean; + due_at: string; + created_by: string; + updated_by: string; + created_at: string; + updated_at: string; } export default function ReportsPage() { const [reports, setReports] = useState([]) const [selectedReport, setSelectedReport] = useState(null) const [isViewModalOpen, setIsViewModalOpen] = useState(false) + const [isLoading, setIsLoading] = useState(true) + const requiredRole = useMemo(() => ["paciente"], []); + const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole }); + useEffect(() => { - const mockReports: Report[] = [ - { - id: "1", - title: "Exame de Sangue - Hemograma Completo", - doctor: "Dr. João Silva", - date: "2024-01-15", - type: "Exame Laboratorial", - status: "disponivel", - description: "Hemograma completo com contagem de células sanguíneas", - content: { - patientInfo: { - name: "Maria Silva Santos", - age: 35, - gender: "Feminino", - id: "123.456.789-00", - }, - examDetails: { - requestingDoctor: "Dr. João Silva - CRM 12345", - examDate: "15/01/2024", - reportDate: "15/01/2024", - technique: "Análise automatizada com confirmação microscópica", - }, - findings: - "Hemácias: 4.5 milhões/mm³ (VR: 4.0-5.2)\nHemoglobina: 13.2 g/dL (VR: 12.0-15.5)\nHematócrito: 40% (VR: 36-46)\nLeucócitos: 7.200/mm³ (VR: 4.000-11.000)\nPlaquetas: 280.000/mm³ (VR: 150.000-450.000)\n\nFórmula leucocitária:\n- Neutrófilos: 65% (VR: 50-70%)\n- Linfócitos: 28% (VR: 20-40%)\n- Monócitos: 5% (VR: 2-8%)\n- Eosinófilos: 2% (VR: 1-4%)", - conclusion: - "Hemograma dentro dos parâmetros normais. Não foram observadas alterações significativas na série vermelha, branca ou plaquetária.", - recommendations: "Manter acompanhamento médico regular. Repetir exame conforme orientação médica.", - }, - }, - { - id: "2", - title: "Radiografia do Tórax", - doctor: "Dra. Maria Santos", - date: "2024-01-10", - type: "Exame de Imagem", - status: "disponivel", - description: "Radiografia PA e perfil do tórax", - content: { - patientInfo: { - name: "Maria Silva Santos", - age: 35, - gender: "Feminino", - id: "123.456.789-00", - }, - examDetails: { - requestingDoctor: "Dra. Maria Santos - CRM 67890", - examDate: "10/01/2024", - reportDate: "10/01/2024", - technique: "Radiografia digital PA e perfil", - }, - findings: - "Campos pulmonares livres, sem sinais de consolidação ou derrame pleural. Silhueta cardíaca dentro dos limites normais. Estruturas ósseas íntegras. Diafragmas em posição normal.", - conclusion: "Radiografia de tórax sem alterações patológicas evidentes.", - recommendations: "Correlacionar com quadro clínico. Acompanhamento conforme indicação médica.", - }, - }, - { - id: "3", - title: "Eletrocardiograma", - doctor: "Dr. Carlos Oliveira", - date: "2024-01-08", - type: "Exame Cardiológico", - status: "pendente", - description: "ECG de repouso para avaliação cardíaca", - content: { - patientInfo: { - name: "Maria Silva Santos", - age: 35, - gender: "Feminino", - id: "123.456.789-00", - }, - examDetails: { - requestingDoctor: "Dr. Carlos Oliveira - CRM 54321", - examDate: "08/01/2024", - reportDate: "", - technique: "ECG de repouso", - }, - findings: "", - conclusion: "", - recommendations: "", - }, - }, - { - id: "4", - title: "Ultrassom Abdominal", - doctor: "Dra. Ana Costa", - date: "2024-01-05", - type: "Exame de Imagem", - status: "disponivel", - description: "Ultrassonografia do abdome total", - content: { - patientInfo: { - name: "Maria Silva Santos", - age: 35, - gender: "Feminino", - id: "123.456.789-00", - }, - examDetails: { - requestingDoctor: "Dra. Ana Costa - CRM 98765", - examDate: "05/01/2024", - reportDate: "05/01/2024", - technique: "Ultrassom convencional", - }, - findings: - "Viscerais bem posicionadas. Rim direito e esquerdo com contornos normais. Vesícula com volume dentro do normal.", - conclusion: "Ultrassom abdominal sem alterações patológicas evidentes.", - recommendations: "Acompanhamento conforme indicação médica.", - }, - }, - ] - setReports(mockReports) - }, []) + if (user) { + const fetchReports = async () => { + try { + setIsLoading(true); + const fetchedReports = await reportsApi.getReports(user.id); + setReports(fetchedReports); + } catch (error) { + console.error("Erro ao buscar laudos:", error) + toast({ + title: "Erro ao buscar laudos", + description: "Não foi possível carregar os laudos. Tente novamente.", + variant: "destructive", + }) + } finally { + setIsLoading(false); + } + } + fetchReports() + } + }, [user?.id]) const handleViewReport = (reportId: string) => { const report = reports.find((r) => r.id === reportId) @@ -168,100 +76,23 @@ export default function ReportsPage() { if (!report) return try { - // Simular loading toast({ title: "Preparando download...", description: "Gerando PDF do laudo médico", }) - // Criar conteúdo HTML do laudo para conversão em PDF - const htmlContent = ` - - - - - Laudo Médico - ${report.title} - - - -
-

LAUDO MÉDICO

-

${report.title}

-

Tipo: ${report.type}

-
- -
-
DADOS DO PACIENTE
-
-
Nome: ${report.content.patientInfo.name}
-
Idade: ${report.content.patientInfo.age} anos
-
Sexo: ${report.content.patientInfo.gender}
-
CPF: ${report.content.patientInfo.id}
-
-
+ const htmlContent = report.content_html; -
-
DETALHES DO EXAME
-
-
Médico Solicitante: ${report.content.examDetails.requestingDoctor}
-
Data do Exame: ${report.content.examDetails.examDate}
-
Data do Laudo: ${report.content.examDetails.reportDate}
-
Técnica: ${report.content.examDetails.technique}
-
-
- -
-
ACHADOS
-
${report.content.findings}
-
- -
-
CONCLUSÃO
-
${report.content.conclusion}
-
- - ${ - report.content.recommendations - ? ` -
-
RECOMENDAÇÕES
-
${report.content.recommendations}
-
- ` - : "" - } - - - - - ` - - // Criar blob com o conteúdo HTML const blob = new Blob([htmlContent], { type: "text/html" }) const url = URL.createObjectURL(blob) - // Criar link temporário para download const link = document.createElement("a") link.href = url - link.download = `laudo-${report.title.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase()}-${report.date}.html` + link.download = `laudo-${report.order_number}.html` document.body.appendChild(link) link.click() document.body.removeChild(link) - // Limpar URL temporária URL.revokeObjectURL(url) toast({ @@ -283,8 +114,18 @@ export default function ReportsPage() { setSelectedReport(null) } - const availableReports = reports.filter((report) => report.status === "disponivel") - const pendingReports = reports.filter((report) => report.status === "pendente") + const availableReports = reports.filter((report) => report.status.toLowerCase() === "draft") + const pendingReports = reports.filter((report) => report.status.toLowerCase() !== "draft") + + if (isLoading || isAuthLoading) { + return ( + +
+ +
+
+ ) + } return ( @@ -333,25 +174,25 @@ export default function ReportsPage() {
- {report.title} + {report.exam} - {report.doctor} + {report.requested_by} - {new Date(report.date).toLocaleDateString("pt-BR")} + {new Date(report.created_at).toLocaleDateString("pt-BR")}
- {report.type} + Finalizado
-

{report.description}

+

{report.diagnosis}

@@ -388,25 +229,25 @@ export default function ReportsPage() {
- {report.title} + {report.exam} - {report.doctor} + {report.requested_by} - {new Date(report.date).toLocaleDateString("pt-BR")} + {new Date(report.created_at).toLocaleDateString("pt-BR")}
- Pendente + {report.status}
-

{report.description}

+

{report.diagnosis}

Laudo em processamento. Você será notificado quando estiver disponível.

@@ -417,7 +258,7 @@ export default function ReportsPage() { )} - {reports.length === 0 && ( + {reports.length === 0 && !isLoading && ( @@ -432,9 +273,9 @@ export default function ReportsPage() {
- {selectedReport?.title} + {selectedReport?.exam} - {selectedReport?.type} - {selectedReport?.doctor} + {selectedReport?.order_number}
- -
- +
)}
) -} +} \ No newline at end of file diff --git a/app/secretary/pacientes/page.tsx b/app/secretary/pacientes/page.tsx index 9990980..60b8dd8 100644 --- a/app/secretary/pacientes/page.tsx +++ b/app/secretary/pacientes/page.tsx @@ -6,44 +6,44 @@ 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 { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2, MoreVertical } from "lucide-react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { patientsService } from "@/services/patientsApi.mjs"; import Sidebar from "@/components/Sidebar"; // Defina o tamanho da página. -const PAGE_SIZE = 5; +const PAGE_SIZE = 5; export default function PacientesPage() { // --- ESTADOS DE DADOS E GERAL --- const [searchTerm, setSearchTerm] = useState(""); const [convenioFilter, setConvenioFilter] = useState("all"); const [vipFilter, setVipFilter] = useState("all"); - - // Lista completa, carregada da API uma única vez - const [allPatients, setAllPatients] = useState([]); - // Lista após a aplicação dos filtros (base para a paginação) - const [filteredPatients, setFilteredPatients] = useState([]); - const [loading, setLoading] = useState(true); + // Lista completa, carregada da API uma única vez + const [allPatients, setAllPatients] = useState([]); + // Lista após a aplicação dos filtros (base para a paginação) + const [filteredPatients, setFilteredPatients] = useState([]); + + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - + // --- ESTADOS DE PAGINAÇÃO --- const [page, setPage] = useState(1); - + // CÁLCULO DA PAGINAÇÃO const totalPages = Math.ceil(filteredPatients.length / PAGE_SIZE); const startIndex = (page - 1) * PAGE_SIZE; const endIndex = startIndex + PAGE_SIZE; // Pacientes a serem exibidos na tabela (aplicando a paginação) - const currentPatients = filteredPatients.slice(startIndex, endIndex); + const currentPatients = filteredPatients.slice(startIndex, endIndex); // --- ESTADOS DE DIALOGS --- const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [patientToDelete, setPatientToDelete] = useState(null); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); const [patientDetails, setPatientDetails] = useState(null); - + // --- FUNÇÕES DE LÓGICA --- // 1. Função para carregar TODOS os pacientes da API @@ -54,7 +54,7 @@ export default function PacientesPage() { try { // Como o backend retorna um array, chamamos sem paginação const res = await patientsService.list(); - + const mapped = res.map((p: any) => ({ id: String(p.id ?? ""), nome: p.full_name ?? "—", @@ -62,7 +62,7 @@ export default function PacientesPage() { cidade: p.city ?? "—", estado: p.state ?? "—", // Formate as datas se necessário, aqui usamos como string - ultimoAtendimento: p.last_visit_at?.split('T')[0] ?? "—", + ultimoAtendimento: p.last_visit_at?.split('T')[0] ?? "—", proximoAtendimento: p.next_appointment_at?.split('T')[0] ?? "—", vip: Boolean(p.vip ?? false), convenio: p.convenio ?? "Particular", // Define um valor padrão @@ -84,27 +84,27 @@ export default function PacientesPage() { useEffect(() => { const filtered = allPatients.filter((patient) => { // Filtro por termo de busca (Nome ou Telefone) - const matchesSearch = - patient.nome?.toLowerCase().includes(searchTerm.toLowerCase()) || + const matchesSearch = + patient.nome?.toLowerCase().includes(searchTerm.toLowerCase()) || patient.telefone?.includes(searchTerm); - + // Filtro por Convênio - const matchesConvenio = - convenioFilter === "all" || + const matchesConvenio = + convenioFilter === "all" || patient.convenio === convenioFilter; - + // Filtro por VIP - const matchesVip = - vipFilter === "all" || - (vipFilter === "vip" && patient.vip) || + const matchesVip = + vipFilter === "all" || + (vipFilter === "vip" && patient.vip) || (vipFilter === "regular" && !patient.vip); return matchesSearch && matchesConvenio && matchesVip; }); - + setFilteredPatients(filtered); // Garante que a página atual seja válida após a filtragem - setPage(1); + setPage(1); }, [allPatients, searchTerm, convenioFilter, vipFilter]); // 3. Efeito inicial para buscar os pacientes @@ -115,7 +115,7 @@ export default function PacientesPage() { // --- LÓGICA DE AÇÕES (DELETAR / VER DETALHES) --- - + const openDetailsDialog = async (patientId: string) => { setDetailsDialogOpen(true); setPatientDetails(null); @@ -164,9 +164,9 @@ export default function PacientesPage() { {/* Bloco de Filtros (Responsividade APLICADA) */} -
+
- + {/* Busca - Ocupa 100% no mobile, depois cresce */} setSearchTerm(e.target.value)} // 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" + className="w-full sm:flex-grow sm:max-w-[300px] p-2 border rounded-md text-sm" /> {/* Convênio - Ocupa a largura total em telas pequenas, depois se ajusta */} @@ -208,7 +208,7 @@ export default function PacientesPage() {
- + {/* Aniversariantes - Ocupa 100% no mobile, e se alinha à direita no md+ */} openDetailsDialog(String(patient.id))}> @@ -309,7 +312,7 @@ export default function PacientesPage() { {/* --- 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 ? ( @@ -329,44 +332,44 @@ export default function PacientesPage() { {patient.nome} {patient.vip && ( VIP - )} + )}
Telefone: {patient.telefone}
Convênio: {patient.convenio}
- - -
-
- - openDetailsDialog(String(patient.id))}> - - Ver detalhes - + + +
+
+ + openDetailsDialog(String(patient.id))}> + + Ver detalhes + - - - - Editar - - + + + + Editar + + - - - Marcar consulta - - openDeleteDialog(String(patient.id))}> - - Excluir - - -
+ + + Marcar consulta + + openDeleteDialog(String(patient.id))}> + + Excluir + +
+
))} )} - + {/* Paginação */} {totalPages > 1 && !loading && (
@@ -405,7 +408,7 @@ export default function PacientesPage() {
)} - + {/* AlertDialogs (Permanecem os mesmos) */} @@ -438,65 +441,65 @@ export default function PacientesPage() {
-

Nome Completo

-

{patientDetails.full_name}

+

Nome Completo

+

{patientDetails.full_name}

-

Email

-

{patientDetails.email}

+

Email

+

{patientDetails.email}

-

Telefone

-

{patientDetails.phone_mobile}

+

Telefone

+

{patientDetails.phone_mobile}

-

Data de Nascimento

-

{patientDetails.birth_date}

+

Data de Nascimento

+

{patientDetails.birth_date}

-

CPF

-

{patientDetails.cpf}

+

CPF

+

{patientDetails.cpf}

-

Tipo Sanguíneo

-

{patientDetails.blood_type}

+

Tipo Sanguíneo

+

{patientDetails.blood_type}

-

Peso (kg)

-

{patientDetails.weight_kg}

+

Peso (kg)

+

{patientDetails.weight_kg}

-

Altura (m)

-

{patientDetails.height_m}

+

Altura (m)

+

{patientDetails.height_m}

Endereço

-
-

Rua

-

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

-
-
-

Complemento

-

{patientDetails.complement}

-
-
-

Bairro

-

{patientDetails.neighborhood}

-
-
-

Cidade

-

{patientDetails.cidade}

-
-
-

Estado

-

{patientDetails.estado}

-
-
-

CEP

-

{patientDetails.cep}

-
+
+

Rua

+

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

+
+
+

Complemento

+

{patientDetails.complement}

+
+
+

Bairro

+

{patientDetails.neighborhood}

+
+
+

Cidade

+

{patientDetails.cidade}

+
+
+

Estado

+

{patientDetails.estado}

+
+
+

CEP

+

{patientDetails.cep}

+
diff --git a/services/reportsApi.mjs b/services/reportsApi.mjs index d3a6445..2c92ea4 100644 --- a/services/reportsApi.mjs +++ b/services/reportsApi.mjs @@ -9,7 +9,7 @@ export const reportsApi = { return data; } catch (error) { console.error("Failed to fetch reports:", error); - return []; + throw error; } }, getReportById: async (reportId) => { From 054e4fddda30630d34f03818c955a4754d4aa0e8 Mon Sep 17 00:00:00 2001 From: StsDanilo Date: Thu, 27 Nov 2025 09:23:32 -0300 Subject: [PATCH 2/2] =?UTF-8?q?Fix=20repeti=C3=A7=C3=A3ao=20de=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/secretary/pacientes/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/secretary/pacientes/page.tsx b/app/secretary/pacientes/page.tsx index 51b0265..cfa13cd 100644 --- a/app/secretary/pacientes/page.tsx +++ b/app/secretary/pacientes/page.tsx @@ -7,9 +7,7 @@ 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, MoreVertical } from "lucide-react"; -import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2, MoreVertical } 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";