diff --git a/app/doctor/medicos/page.tsx b/app/doctor/medicos/page.tsx index 7973a65..484ad9c 100644 --- a/app/doctor/medicos/page.tsx +++ b/app/doctor/medicos/page.tsx @@ -2,12 +2,7 @@ import { useEffect, useState, useCallback } from "react"; 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, MoreVertical } from "lucide-react"; import { api } from "@/services/api.mjs"; import { PatientDetailsModal } from "@/components/ui/patient-details-modal"; @@ -200,210 +195,181 @@ export default function PacientesPage() { -
- {/* 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 - - - { - 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 - - - -
-
+
+ {/* 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 || "—"} + {/* 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 + + + +
+
+ )) + )}
- {/* 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 && ( @@ -445,11 +411,11 @@ export default function PacientesPage() {
- - - ); + + + ); } diff --git a/app/manager/home/page.tsx b/app/manager/home/page.tsx index 720398e..f4ed3a0 100644 --- a/app/manager/home/page.tsx +++ b/app/manager/home/page.tsx @@ -3,14 +3,13 @@ import React, { useEffect, useState, useCallback, useMemo } from "react"; 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 { Edit, Trash2, Eye, Calendar, Loader2, MoreVertical } from "lucide-react"; -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; +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, MoreVertical } from "lucide-react" +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog" -// Imports dos Serviços -import { doctorsService } from "@/services/doctorsApi.mjs"; +import { doctorsService } from "services/doctorsApi.mjs"; import Sidebar from "@/components/Sidebar"; // --- NOVOS IMPORTS (Certifique-se que criou os arquivos no passo anterior) --- @@ -326,9 +325,10 @@ export default function DoctorsPage() { -
+
+
openDetailsDialog(doctor)}> diff --git a/app/manager/pacientes/page.tsx b/app/manager/pacientes/page.tsx index e2938ee..b4cb6df 100644 --- a/app/manager/pacientes/page.tsx +++ b/app/manager/pacientes/page.tsx @@ -3,39 +3,10 @@ import { useState, useEffect, useCallback } from "react"; 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, - MoreVertical, -} from "lucide-react"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog"; +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, 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"; @@ -43,60 +14,59 @@ import Sidebar from "@/components/Sidebar"; 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"); + // --- 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([]); + // 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); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - // --- ESTADOS DE PAGINAÇÃO --- - const [page, setPage] = useState(1); + // --- 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); + // 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); - // --- ESTADOS DE DIALOGS --- - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [patientToDelete, setPatientToDelete] = useState(null); - const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); - const [patientDetails, setPatientDetails] = useState(null); + // --- 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 --- + // --- FUNÇÕES DE LÓGICA --- - // 1. Função para carregar TODOS os pacientes da API - const fetchAllPacientes = useCallback(async () => { - setLoading(true); - setError(null); - try { - // Como o backend retorna um array, chamamos sem paginação - const res = await patientsService.list(); + // 1. Função para carregar TODOS os pacientes da API + const fetchAllPacientes = useCallback( + async () => { + setLoading(true); + setError(null); + 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 ?? "—", - telefone: p.phone_mobile ?? p.phone1 ?? "—", - cidade: p.city ?? "—", - estado: p.state ?? "—", - // Formate as datas se necessário, aqui usamos como string - ultimoAtendimento: p.last_visit_at?.split("T")[0] ?? "—", - proximoAtendimento: p.next_appointment_at - ? p.next_appointment_at.split("T")[0].split("-").reverse().join("-") - : "—", - vip: Boolean(p.vip ?? false), - convenio: p.convenio ?? "Particular", // Define um valor padrão - status: p.status ?? undefined, - })); + const mapped = res.map((p: any) => ({ + id: String(p.id ?? ""), + nome: p.full_name ?? "—", + telefone: p.phone_mobile ?? p.phone1 ?? "—", + cidade: p.city ?? "—", + estado: p.state ?? "—", + // Formate as datas se necessário, aqui usamos como string + 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 + status: p.status ?? undefined, + })); setAllPatients(mapped); } catch (e: any) { @@ -107,31 +77,32 @@ export default function PacientesPage() { } }, []); - // 2. Efeito para aplicar filtros e calcular a lista filtrada (chama-se quando allPatients ou filtros mudam) - useEffect(() => { - const filtered = allPatients.filter((patient) => { - // Filtro por termo de busca (Nome ou Telefone) - const matchesSearch = - patient.nome?.toLowerCase().includes(searchTerm.toLowerCase()) || - patient.telefone?.includes(searchTerm); + // 2. Efeito para aplicar filtros e calcular a lista filtrada (chama-se quando allPatients ou filtros mudam) + useEffect(() => { + const filtered = allPatients.filter((patient) => { + // Filtro por termo de busca (Nome ou Telefone) + const matchesSearch = + patient.nome?.toLowerCase().includes(searchTerm.toLowerCase()) || + patient.telefone?.includes(searchTerm); - // Filtro por Convênio - const matchesConvenio = - convenioFilter === "all" || patient.convenio === convenioFilter; + // Filtro por Convênio + const matchesConvenio = + convenioFilter === "all" || + patient.convenio === convenioFilter; - // Filtro por VIP - const matchesVip = - vipFilter === "all" || - (vipFilter === "vip" && patient.vip) || - (vipFilter === "regular" && !patient.vip); + // Filtro por VIP + const matchesVip = + vipFilter === "all" || + (vipFilter === "vip" && patient.vip) || + (vipFilter === "regular" && !patient.vip); - return matchesSearch && matchesConvenio && matchesVip; - }); + return matchesSearch && matchesConvenio && matchesVip; + }); - setFilteredPatients(filtered); - // Garante que a página atual seja válida após a filtragem - setPage(1); - }, [allPatients, searchTerm, convenioFilter, vipFilter]); + setFilteredPatients(filtered); + // Garante que a página atual seja válida após a filtragem + setPage(1); + }, [allPatients, searchTerm, convenioFilter, vipFilter]); // 3. Efeito inicial para buscar os pacientes useEffect(() => { @@ -139,18 +110,18 @@ export default function PacientesPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // --- LÓGICA DE AÇÕES (DELETAR / VER DETALHES) --- + // --- LÓGICA DE AÇÕES (DELETAR / VER DETALHES) --- - const openDetailsDialog = async (patientId: string) => { - setDetailsDialogOpen(true); - setPatientDetails(null); - try { - const res = await patientsService.getById(patientId); - setPatientDetails(Array.isArray(res) ? res[0] : res); // Supondo que retorne um array com um item - } catch (e: any) { - setPatientDetails({ error: e?.message || "Erro ao buscar detalhes" }); - } - }; + const openDetailsDialog = async (patientId: string) => { + setDetailsDialogOpen(true); + setPatientDetails(null); + try { + const res = await patientsService.getById(patientId); + setPatientDetails(Array.isArray(res) ? res[0] : res); // Supondo que retorne um array com um item + } catch (e: any) { + setPatientDetails({ error: e?.message || "Erro ao buscar detalhes" }); + } + }; const handleDeletePatient = async (patientId: string) => { try { @@ -186,20 +157,20 @@ export default function PacientesPage() {
- {/* Bloco de Filtros (Responsividade APLICADA) */} - {/* Adicionado flex-wrap para permitir que os itens quebrem para a linha de baixo */} -
- + {/* Bloco de Filtros (Responsividade APLICADA) */} + {/* 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" - /> + {/* 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" + /> {/* Convênio - Ocupa a largura total em telas pequenas, depois se ajusta */}
@@ -222,31 +193,22 @@ export default function PacientesPage() {
- {/* VIP - Ocupa a largura total em telas pequenas, depois se ajusta */} -
- - VIP - - -
+ {/* VIP - Ocupa a largura total em telas pequenas, depois se ajusta */} +
+ VIP + +
- {/* Aniversariantes - Ocupa 100% no mobile, e se alinha à direita no md+ */} - -
+
{/* --- 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+ */} 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 4a68031..cfa13cd 100644 --- a/app/secretary/pacientes/page.tsx +++ b/app/secretary/pacientes/page.tsx @@ -8,7 +8,6 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; 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"; @@ -417,103 +416,83 @@ export default function PacientesPage() { Ver detalhes - - - - Editar - - + + + + Editar + + - - - Marcar consulta - - openDeleteDialog(String(patient.id))} - > - - Excluir - - - + + + 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) => ( - - ))} - -
-
- )} + {/* Paginação */} + {totalPages > 1 && !loading && ( +
+
{/* Adicionado flex-wrap e justify-center para botões da paginação */} + - {/* AlertDialogs (Permanecem os mesmos) */} - - - - Confirmar exclusão - - Tem certeza que deseja excluir este paciente? Esta ação não pode - ser desfeita. - - - - Cancelar - - patientToDelete && handleDeletePatient(patientToDelete) - } - className="bg-red-600 hover:bg-red-700" - > - Excluir - - - - + {Array.from({ length: totalPages }, (_, index) => index + 1) + .slice(Math.max(0, page - 3), Math.min(totalPages, page + 2)) + .map((pageNumber) => ( + + ))} + + +
+
+ )} + + {/* AlertDialogs (Permanecem os mesmos) */} + + + + Confirmar exclusão + Tem certeza que deseja excluir este paciente? Esta ação não pode ser desfeita. + + + Cancelar + patientToDelete && handleDeletePatient(patientToDelete)} className="bg-red-600 hover:bg-red-700"> + Excluir + + + + {