From 63fc99c15156d65d92bd167f2fec0613a838d983 Mon Sep 17 00:00:00 2001 From: m1guelmcf Date: Tue, 4 Nov 2025 09:12:45 -0300 Subject: [PATCH] ajuste do filtro de paciente --- app/secretary/pacientes/page.tsx | 804 +++++++++++++++++-------------- 1 file changed, 439 insertions(+), 365 deletions(-) diff --git a/app/secretary/pacientes/page.tsx b/app/secretary/pacientes/page.tsx index 3715261..ca02124 100644 --- a/app/secretary/pacientes/page.tsx +++ b/app/secretary/pacientes/page.tsx @@ -3,391 +3,465 @@ import { useState, useEffect, useRef, 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 { + 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 } from "lucide-react"; -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; +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"; export default function PacientesPage() { - const [searchTerm, setSearchTerm] = useState(""); - const [convenioFilter, setConvenioFilter] = useState("all"); - const [vipFilter, setVipFilter] = useState("all"); - const [patients, setPatients] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [page, setPage] = useState(1); - const [hasNext, setHasNext] = useState(true); - const [isFetching, setIsFetching] = useState(false); - const observerRef = useRef(null); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [patientToDelete, setPatientToDelete] = useState(null); - const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); - const [patientDetails, setPatientDetails] = useState(null); - const openDetailsDialog = async (patientId: string) => { - setDetailsDialogOpen(true); - setPatientDetails(null); - try { - const res = await patientsService.getById(patientId); - setPatientDetails(res[0]); - } catch (e: any) { - setPatientDetails({ error: e?.message || "Erro ao buscar detalhes" }); - } - }; + const [searchTerm, setSearchTerm] = useState(""); + const [convenioFilter, setConvenioFilter] = useState("all"); + const [vipFilter, setVipFilter] = useState("all"); + const [patients, setPatients] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [page, setPage] = useState(1); + const [hasNext, setHasNext] = useState(true); + const [isFetching, setIsFetching] = useState(false); + const observerRef = useRef(null); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [patientToDelete, setPatientToDelete] = useState(null); + const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); + const [patientDetails, setPatientDetails] = useState(null); + const openDetailsDialog = async (patientId: string) => { + setDetailsDialogOpen(true); + setPatientDetails(null); + try { + const res = await patientsService.getById(patientId); + setPatientDetails(res[0]); + } catch (e: any) { + setPatientDetails({ error: e?.message || "Erro ao buscar detalhes" }); + } + }; - const fetchPacientes = useCallback( - async (pageToFetch: number) => { - if (isFetching || !hasNext) return; - setIsFetching(true); - setError(null); - try { - 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 ?? "", - ultimoAtendimento: p.last_visit_at ?? "", - proximoAtendimento: p.next_appointment_at ?? "", - vip: Boolean(p.vip ?? false), - convenio: p.convenio ?? "", // se não existir, fica vazio - status: p.status ?? undefined, - })); + const fetchPacientes = useCallback( + async (pageToFetch: number) => { + if (isFetching || !hasNext) return; + setIsFetching(true); + setError(null); + try { + 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 ?? "", + ultimoAtendimento: p.last_visit_at ?? "", + proximoAtendimento: p.next_appointment_at ?? "", + vip: Boolean(p.vip ?? false), + convenio: p.convenio ?? "", // se não existir, fica vazio + status: p.status ?? undefined, + })); - setPatients((prev) => { - const all = [...prev, ...mapped]; - const unique = Array.from(new Map(all.map((p) => [p.id, p])).values()); - return unique; - }); - - if (!mapped.id) setHasNext(false); // parar carregamento - else setPage((prev) => prev + 1); - } catch (e: any) { - setError(e?.message || "Erro ao buscar pacientes"); - } finally { - setIsFetching(false); - } - }, - [isFetching, hasNext] - ); - - useEffect(() => { - fetchPacientes(page); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (!observerRef.current || !hasNext) return; - const observer = new window.IntersectionObserver((entries) => { - if (entries[0].isIntersecting && !isFetching && hasNext) { - fetchPacientes(page); - } + setPatients((prev) => { + const all = [...prev, ...mapped]; + const unique = Array.from( + new Map(all.map((p) => [p.id, p])).values() + ); + return unique; }); - observer.observe(observerRef.current); - return () => { - if (observerRef.current) observer.unobserve(observerRef.current); - }; - }, [fetchPacientes, page, hasNext, isFetching]); - const handleDeletePatient = async (patientId: string) => { - // Remove from current list (client-side deletion) - try { - const res = await patientsService.delete(patientId); + if (!mapped.id) setHasNext(false); // parar carregamento + else setPage((prev) => prev + 1); + } catch (e: any) { + setError(e?.message || "Erro ao buscar pacientes"); + } finally { + setIsFetching(false); + } + }, + [isFetching, hasNext] + ); - if (res) { - alert(`${res.error} ${res.message}`); - } + useEffect(() => { + fetchPacientes(page); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - setPatients((prev) => prev.filter((p) => String(p.id) !== String(patientId))); - } catch (e: any) { - setError(e?.message || "Erro ao deletar paciente"); - } - setDeleteDialogOpen(false); - setPatientToDelete(null); - }; - - const openDeleteDialog = (patientId: string) => { - setPatientToDelete(patientId); - setDeleteDialogOpen(true); - }; - - const filteredPatients = patients.filter((patient) => { - const matchesSearch = patient.nome?.toLowerCase().includes(searchTerm.toLowerCase()) || patient.telefone?.includes(searchTerm); - const matchesConvenio = convenioFilter === "all" || (patient.convenio ?? "") === convenioFilter; - const matchesVip = vipFilter === "all" || (vipFilter === "vip" && patient.vip) || (vipFilter === "regular" && !patient.vip); - - return matchesSearch && matchesConvenio && matchesVip; + useEffect(() => { + if (!observerRef.current || !hasNext) return; + const observer = new window.IntersectionObserver((entries) => { + if (entries[0].isIntersecting && !isFetching && hasNext) { + fetchPacientes(page); + } }); + observer.observe(observerRef.current); + return () => { + if (observerRef.current) observer.unobserve(observerRef.current); + }; + }, [fetchPacientes, page, hasNext, isFetching]); - return ( - -
-
-
-

Pacientes

-

Gerencie as informações de seus pacientes

-
-
- - - -
-
+ const handleDeletePatient = async (patientId: string) => { + // Remove from current list (client-side deletion) + try { + const res = await patientsService.delete(patientId); -
- {/* Convênio */} -
- Convênio - -
+ if (res) { + alert(`${res.error} ${res.message}`); + } -
- VIP - -
-
- Aniversariantes - -
+ setPatients((prev) => + prev.filter((p) => String(p.id) !== String(patientId)) + ); + } catch (e: any) { + setError(e?.message || "Erro ao deletar paciente"); + } + setDeleteDialogOpen(false); + setPatientToDelete(null); + }; -
- VIP - -
+ const openDeleteDialog = (patientId: string) => { + setPatientToDelete(patientId); + setDeleteDialogOpen(true); + }; -
- Aniversariantes - -
+ const filteredPatients = patients.filter((patient) => { + const matchesSearch = + patient.nome?.toLowerCase().includes(searchTerm.toLowerCase()) || + patient.telefone?.includes(searchTerm); + const matchesConvenio = + convenioFilter === "all" || (patient.convenio ?? "") === convenioFilter; + const matchesVip = + vipFilter === "all" || + (vipFilter === "vip" && patient.vip) || + (vipFilter === "regular" && !patient.vip); - -
+ return matchesSearch && matchesConvenio && matchesVip; + }); -
-
- {error ? ( -
{`Erro ao carregar pacientes: ${error}`}
- ) : ( - - - - - - - - - - - - - - {filteredPatients.length === 0 ? ( - - - - ) : ( - filteredPatients.map((patient) => ( - - - - - - - - - - )) - )} - -
NomeTelefoneCidadeEstadoÚltimo atendimentoPróximo atendimentoAções
- {patients.length === 0 ? "Nenhum paciente cadastrado" : "Nenhum paciente encontrado com os filtros aplicados"} -
-
-
- {patient.nome?.charAt(0) || "?"} -
- {patient.nome} -
-
{patient.telefone}{patient.cidade}{patient.estado}{patient.ultimoAtendimento}{patient.proximoAtendimento} - - -
Ações
-
- - openDetailsDialog(String(patient.id))}> - - Ver detalhes - - - - - Editar - - - - - Marcar consulta - - openDeleteDialog(String(patient.id))}> - - Excluir - - -
-
- )} -
- {isFetching &&
Carregando mais pacientes...
} -
-
+ return ( + +
+
+
+

+ Pacientes +

+

+ Gerencie as informações de seus pacientes +

+
+
+ + + +
+
- - - - 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"> +
+ {/* Convênio */} +
+ + Convênio + + +
+ +
+ VIP + +
+
+ + Aniversariantes + + +
+ + +
+ +
+
+ {error ? ( +
{`Erro ao carregar pacientes: ${error}`}
+ ) : ( + + + + + + + + + + + + + + {filteredPatients.length === 0 ? ( + + + + ) : ( + filteredPatients.map((patient) => ( + + + + + + + + + + )) + )} + +
+ Nome + + Telefone + + Cidade + + Estado + + Último atendimento + + Próximo atendimento + + Ações +
+ {patients.length === 0 + ? "Nenhum paciente cadastrado" + : "Nenhum paciente encontrado com os filtros aplicados"} +
+
+
+ + {patient.nome?.charAt(0) || "?"} + +
+ + {patient.nome} + +
+
+ {patient.telefone} + {patient.cidade}{patient.estado} + {patient.ultimoAtendimento} + + {patient.proximoAtendimento} + + + +
Ações
+
+ + + openDetailsDialog(String(patient.id)) + } + > + + Ver detalhes + + + + + Editar + + + + + Marcar consulta + + + openDeleteDialog(String(patient.id)) + } + > + Excluir - - - - + + +
+
+ )} +
+ {isFetching && ( +
+ Carregando mais pacientes... +
+ )} +
+
- {/* Modal de detalhes do paciente */} - - - - Detalhes do Paciente - - {patientDetails === null ? ( -
Carregando...
- ) : patientDetails?.error ? ( -
{patientDetails.error}
- ) : ( -
-

- Nome: {patientDetails.full_name} -

-

- CPF: {patientDetails.cpf} -

-

- Email: {patientDetails.email} -

-

- Telefone: {patientDetails.phone_mobile ?? patientDetails.phone1 ?? patientDetails.phone2 ?? "-"} -

-

- Nome social: {patientDetails.social_name ?? "-"} -

-

- Sexo: {patientDetails.sex ?? "-"} -

-

- Tipo sanguíneo: {patientDetails.blood_type ?? "-"} -

-

- Peso: {patientDetails.weight_kg ?? "-"} - {patientDetails.weight_kg ? "kg" : ""} -

-

- Altura: {patientDetails.height_m ?? "-"} - {patientDetails.height_m ? "m" : ""} -

-

- IMC: {patientDetails.bmi ?? "-"} -

-

- Endereço: {patientDetails.street ?? "-"} -

-

- Bairro: {patientDetails.neighborhood ?? "-"} -

-

- Cidade: {patientDetails.city ?? "-"} -

-

- Estado: {patientDetails.state ?? "-"} -

-

- CEP: {patientDetails.cep ?? "-"} -

-

- Criado em: {patientDetails.created_at ?? "-"} -

-

- Atualizado em: {patientDetails.updated_at ?? "-"} -

-

- Id: {patientDetails.id ?? "-"} -

-
- )} -
-
- - Fechar - -
-
-
- - ); + + + + 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 + + + + + + {/* Modal de detalhes do paciente */} + + + + Detalhes do Paciente + + {patientDetails === null ? ( +
Carregando...
+ ) : patientDetails?.error ? ( +
{patientDetails.error}
+ ) : ( +
+

+ Nome: {patientDetails.full_name} +

+

+ CPF: {patientDetails.cpf} +

+

+ Email: {patientDetails.email} +

+

+ Telefone:{" "} + {patientDetails.phone_mobile ?? + patientDetails.phone1 ?? + patientDetails.phone2 ?? + "-"} +

+

+ Nome social:{" "} + {patientDetails.social_name ?? "-"} +

+

+ Sexo: {patientDetails.sex ?? "-"} +

+

+ Tipo sanguíneo:{" "} + {patientDetails.blood_type ?? "-"} +

+

+ Peso: {patientDetails.weight_kg ?? "-"} + {patientDetails.weight_kg ? "kg" : ""} +

+

+ Altura: {patientDetails.height_m ?? "-"} + {patientDetails.height_m ? "m" : ""} +

+

+ IMC: {patientDetails.bmi ?? "-"} +

+

+ Endereço: {patientDetails.street ?? "-"} +

+

+ Bairro:{" "} + {patientDetails.neighborhood ?? "-"} +

+

+ Cidade: {patientDetails.city ?? "-"} +

+

+ Estado: {patientDetails.state ?? "-"} +

+

+ CEP: {patientDetails.cep ?? "-"} +

+

+ Criado em:{" "} + {patientDetails.created_at ?? "-"} +

+

+ Atualizado em:{" "} + {patientDetails.updated_at ?? "-"} +

+

+ Id: {patientDetails.id ?? "-"} +

+
+ )} +
+
+ + Fechar + +
+
+
+
+ ); }