From 569d91298143cc1c1b188d6d6a69394423cdaf59 Mon Sep 17 00:00:00 2001 From: GagoDuBroca Date: Thu, 27 Nov 2025 16:12:15 -0300 Subject: [PATCH] =?UTF-8?q?Adi=C3=A7=C3=A3o=20de=20pagina=C3=A7=C3=A3o,=20?= =?UTF-8?q?ajuste=20de=20cor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/doctor/disponibilidade/page.tsx | 2 +- app/manager/pacientes/page.tsx | 724 +++++++++++++--------------- app/patient/dashboard/page.tsx | 2 +- 3 files changed, 341 insertions(+), 387 deletions(-) diff --git a/app/doctor/disponibilidade/page.tsx b/app/doctor/disponibilidade/page.tsx index 7fd25fd..9dd35b8 100644 --- a/app/doctor/disponibilidade/page.tsx +++ b/app/doctor/disponibilidade/page.tsx @@ -551,7 +551,7 @@ export default function AvailabilityPage() { - diff --git a/app/manager/pacientes/page.tsx b/app/manager/pacientes/page.tsx index b4cb6df..0902af3 100644 --- a/app/manager/pacientes/page.tsx +++ b/app/manager/pacientes/page.tsx @@ -10,9 +10,6 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, import { patientsService } from "@/services/patientsApi.mjs"; import Sidebar from "@/components/Sidebar"; -// Defina o tamanho da página. -const PAGE_SIZE = 5; - export default function PacientesPage() { // --- ESTADOS DE DADOS E GERAL --- const [searchTerm, setSearchTerm] = useState(""); @@ -29,11 +26,15 @@ export default function PacientesPage() { // --- ESTADOS DE PAGINAÇÃO --- const [page, setPage] = useState(1); + + // PADRONIZAÇÃO: Iniciar com 10 itens por página + const [pageSize, setPageSize] = useState(10); // CÁLCULO DA PAGINAÇÃO - const totalPages = Math.ceil(filteredPatients.length / PAGE_SIZE); - const startIndex = (page - 1) * PAGE_SIZE; - const endIndex = startIndex + PAGE_SIZE; + const totalPages = Math.ceil(filteredPatients.length / pageSize); + const startIndex = (page - 1) * pageSize; + const endIndex = startIndex + pageSize; + // Pacientes a serem exibidos na tabela (aplicando a paginação) const currentPatients = filteredPatients.slice(startIndex, endIndex); @@ -51,7 +52,6 @@ export default function PacientesPage() { 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) => ({ @@ -60,37 +60,33 @@ export default function PacientesPage() { 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 + convenio: p.convenio ?? "Particular", status: p.status ?? undefined, })); - setAllPatients(mapped); - } catch (e: any) { - console.error(e); - setError(e?.message || "Erro ao buscar pacientes"); - } finally { - setLoading(false); - } - }, []); + setAllPatients(mapped); + } catch (e: any) { + console.error(e); + setError(e?.message || "Erro ao buscar pacientes"); + } finally { + setLoading(false); + } + }, []); - // 2. Efeito para aplicar filtros e calcular a lista filtrada (chama-se quando allPatients ou filtros mudam) + // 2. Efeito para aplicar filtros 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 VIP const matchesVip = vipFilter === "all" || (vipFilter === "vip" && patient.vip) || @@ -100,104 +96,97 @@ export default function PacientesPage() { }); setFilteredPatients(filtered); - // Garante que a página atual seja válida após a filtragem - setPage(1); + setPage(1); // Reseta a página ao filtrar }, [allPatients, searchTerm, convenioFilter, vipFilter]); - // 3. Efeito inicial para buscar os pacientes - useEffect(() => { - fetchAllPacientes(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + // 3. Efeito inicial + useEffect(() => { + fetchAllPacientes(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - // --- LÓGICA DE AÇÕES (DELETAR / VER DETALHES) --- + // --- LÓGICA DE AÇÕES --- 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 + setPatientDetails(Array.isArray(res) ? res[0] : res); } catch (e: any) { setPatientDetails({ error: e?.message || "Erro ao buscar detalhes" }); } }; - const handleDeletePatient = async (patientId: string) => { - try { - await patientsService.delete(patientId); - // Atualiza a lista completa para refletir a exclusão - setAllPatients((prev) => - prev.filter((p) => String(p.id) !== String(patientId)) - ); - } catch (e: any) { - alert(`Erro ao deletar paciente: ${e?.message || "Erro desconhecido"}`); - } - setDeleteDialogOpen(false); - setPatientToDelete(null); - }; + const handleDeletePatient = async (patientId: string) => { + try { + await patientsService.delete(patientId); + setAllPatients((prev) => + prev.filter((p) => String(p.id) !== String(patientId)) + ); + } catch (e: any) { + alert(`Erro ao deletar paciente: ${e?.message || "Erro desconhecido"}`); + } + setDeleteDialogOpen(false); + setPatientToDelete(null); + }; - const openDeleteDialog = (patientId: string) => { - setPatientToDelete(patientId); - setDeleteDialogOpen(true); - }; + const openDeleteDialog = (patientId: string) => { + setPatientToDelete(patientId); + setDeleteDialogOpen(true); + }; - return ( - -
- {/* Header (Responsividade OK) */} -
-
-

- Pacientes -

-

- Gerencie as informações de seus pacientes -

-
-
+ return ( + +
+ {/* Header */} +
+
+

+ Pacientes +

+

+ Gerencie as informações de seus pacientes +

+
+
- {/* Bloco de Filtros (Responsividade APLICADA) */} - {/* Adicionado flex-wrap para permitir que os itens quebrem para a linha de baixo */} + {/* Filtros */}
- {/* Busca - Ocupa 100% no mobile, depois cresce */} + {/* Busca */} 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 */} -
- - Convênio - - -
+ {/* Convênio */} +
+ + Convênio + + +
- {/* VIP - Ocupa a largura total em telas pequenas, depois se ajusta */} + {/* VIP */}
VIP
- + + {/* Seletor de Itens por Página (Inicia com 10) */} +
+ +
- {/* --- 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 ? ( -
- {" "} - Carregando pacientes... -
- ) : ( - - {" "} - {/* min-w para evitar que a tabela se contraia demais */} - - - - {/* Ajustes de visibilidade de colunas para diferentes breakpoints */} - - - - - - - - - - {currentPatients.length === 0 ? ( - - - - ) : ( - currentPatients.map((patient) => ( - - - - - - - - - - - )) - )} - -
- Nome - - Telefone - - Cidade / Estado - - Convênio - - Último atendimento - - Próximo atendimento - - Ações -
- {allPatients.length === 0 - ? "Nenhum paciente cadastrado" - : "Nenhum paciente encontrado com os filtros aplicados"} -
-
-
- - {patient.nome?.charAt(0) || "?"} - + {/* Tabela */} +
+
+ {error ? ( +
{`Erro ao carregar pacientes: ${error}`}
+ ) : loading ? ( +
+ {" "} + Carregando pacientes...
- - {patient.nome} - {patient.vip && ( - - VIP - - )} - -
-
- {patient.telefone} - {`${patient.cidade} / ${patient.estado}`} - {patient.convenio} - - {patient.ultimoAtendimento} - - {patient.proximoAtendimento} - - - -
- -
-
- - - openDetailsDialog(String(patient.id)) - } - > - - Ver detalhes - - - - - Editar - - - - - - 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) */} - - - - 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 - - - - - - - - - Detalhes do Paciente - - {patientDetails === null ? ( -
- - Carregando... -
- ) : patientDetails?.error ? ( -
{patientDetails.error}
- ) : ( -
-
-
-

Nome Completo

-

{patientDetails.full_name}

-
-
-

Email

-

{patientDetails.email}

-
-
-

Telefone

-

{patientDetails.phone_mobile}

-
-
-

Data de Nascimento

-

{patientDetails.birth_date}

-
-
-

CPF

-

{patientDetails.cpf}

-
-
-

Tipo Sanguíneo

-

{patientDetails.blood_type}

-
-
-

Peso (kg)

-

{patientDetails.weight_kg}

-
-
-

Altura (m)

-

{patientDetails.height_m}

-
+ ) : ( + + + + + + + + + + + + + + {currentPatients.length === 0 ? ( + + + + ) : ( + currentPatients.map((patient) => ( + + + + + + + + + + )) + )} + +
NomeTelefoneCidade / EstadoConvênioÚltimo atendimentoPróximo atendimentoAções
+ {allPatients.length === 0 + ? "Nenhum paciente cadastrado" + : "Nenhum paciente encontrado com os filtros aplicados"} +
+
+
+ + {patient.nome?.charAt(0) || "?"} + +
+ + {patient.nome} + {patient.vip && ( + + VIP + + )} + +
+
{patient.telefone}{`${patient.cidade} / ${patient.estado}`}{patient.convenio}{patient.ultimoAtendimento}{patient.proximoAtendimento} + + +
+ +
+
+ + openDetailsDialog(String(patient.id))}> + + Ver detalhes + + + + + Editar + + + + + Marcar consulta + + openDeleteDialog(String(patient.id))}> + + Excluir + + +
+
+ )}
-
-

Endereço

-
-
-

Rua

-

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

+
+ + {/* Paginação */} + {totalPages > 1 && !loading && ( +
+
+ + {Array.from({ length: totalPages }, (_, index) => index + 1) + .slice(Math.max(0, page - 3), Math.min(totalPages, page + 2)) + .map((pageNumber) => ( + + ))} +
-
-

Complemento

-

{patientDetails.complement}

-
-
-

Bairro

-

{patientDetails.neighborhood}

-
-
-

Cidade

-

{patientDetails.cidade}

-
-
-

Estado

-

{patientDetails.estado}

-
-
-

CEP

-

{patientDetails.cep}

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

Nome Completo

+

{patientDetails.full_name}

+
+
+

Email

+

{patientDetails.email}

+
+
+

Telefone

+

{patientDetails.phone_mobile}

+
+
+

Data de Nascimento

+

{patientDetails.birth_date}

+
+
+

CPF

+

{patientDetails.cpf}

+
+
+

Tipo Sanguíneo

+

{patientDetails.blood_type}

+
+
+

Peso (kg)

+

{patientDetails.weight_kg}

+
+
+

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}

+
+
+
+
+ )} +
+
+ + Fechar + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/patient/dashboard/page.tsx b/app/patient/dashboard/page.tsx index 6573d1e..081305a 100644 --- a/app/patient/dashboard/page.tsx +++ b/app/patient/dashboard/page.tsx @@ -80,7 +80,7 @@ export default function PatientDashboard() {