From 96b8b62d6ad75be27fbed3b2c7c9a9db0b839ee0 Mon Sep 17 00:00:00 2001 From: GagoDuBroca Date: Mon, 10 Nov 2025 20:44:42 -0300 Subject: [PATCH 1/8] Ajuste De Tabelas --- app/doctor/medicos/page.tsx | 714 +++++++++++++++++-------------- app/manager/home/page.tsx | 173 +++++--- app/manager/pacientes/page.tsx | 172 +++++--- app/manager/usuario/page.tsx | 81 ++-- app/secretary/pacientes/page.tsx | 172 +++++--- 5 files changed, 768 insertions(+), 544 deletions(-) diff --git a/app/doctor/medicos/page.tsx b/app/doctor/medicos/page.tsx index bc18221..c5d020e 100644 --- a/app/doctor/medicos/page.tsx +++ b/app/doctor/medicos/page.tsx @@ -1,368 +1,432 @@ -// 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, + 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, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; 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/manager/home/page.tsx b/app/manager/home/page.tsx index 03b64a5..b1f8d70 100644 --- a/app/manager/home/page.tsx +++ b/app/manager/home/page.tsx @@ -1,13 +1,13 @@ "use client"; -import React, { useEffect, useState, useCallback, useMemo } from "react" +import React, { useEffect, useState, useCallback, useMemo } from "react"; import ManagerLayout from "@/components/manager-layout"; -import Link from "next/link" +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 { 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, @@ -17,11 +17,10 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog" +} from "@/components/ui/alert-dialog"; import { doctorsService } from "services/doctorsApi.mjs"; - interface Doctor { id: number; full_name: string; @@ -33,7 +32,6 @@ interface Doctor { status?: string; } - interface DoctorDetails { nome: string; crm: string; @@ -41,11 +39,11 @@ interface DoctorDetails { contato: { celular?: string; telefone1?: string; - } + }; endereco: { cidade?: string; estado?: string; - } + }; convenio?: string; vip?: boolean; status?: string; @@ -80,7 +78,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 +91,10 @@ export default function DoctorsPage() { } }, []); - useEffect(() => { fetchDoctors(); }, [fetchDoctors]); - const openDetailsDialog = async (doctor: Doctor) => { setDetailsDialogOpen(true); setDoctorDetails({ @@ -115,7 +111,6 @@ export default function DoctorsPage() { }); }; - const handleDelete = async () => { if (doctorToDeleteId === null) return; setLoading(true); @@ -138,11 +133,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 +186,9 @@ export default function DoctorsPage() { setCurrentPage(1); }; - return (
- {/* Cabeçalho */}
@@ -204,29 +197,26 @@ export default function DoctorsPage() {
- {/* Filtros e Itens por Página */}
- - Especialidade - + Especialidade
- - Status - + Status
- - Itens por página - - @@ -262,9 +248,8 @@ export default function DoctorsPage() {
- - {/* Tabela de Médicos */} -
+ {/* Tabela de Médicos (Visível em Telas Médias e Maiores) */} +
{loading ? (
@@ -287,8 +272,8 @@ export default function DoctorsPage() { Nome CRM Especialidade - Status - Cidade/Estado + Status + Cidade/Estado Ações @@ -305,7 +290,6 @@ export default function DoctorsPage() { : "N/A"} - {/* ===== INÍCIO DA ALTERAÇÃO ===== */}
Ações
@@ -331,7 +315,6 @@ export default function DoctorsPage() {
- {/* ===== FIM DA ALTERAÇÃO ===== */} ))} @@ -341,6 +324,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 +394,11 @@ export default function DoctorsPage() { @@ -380,9 +419,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 +440,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.
} diff --git a/app/manager/pacientes/page.tsx b/app/manager/pacientes/page.tsx index 5fb159b..2d034aa 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"; @@ -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}`}

diff --git a/app/manager/usuario/page.tsx b/app/manager/usuario/page.tsx index 805fb0c..9a65e1f 100644 --- a/app/manager/usuario/page.tsx +++ b/app/manager/usuario/page.tsx @@ -1,4 +1,3 @@ -// app/manager/usuario/page.tsx "use client"; import React, { useEffect, useState, useCallback } from "react"; @@ -22,8 +21,8 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; -import { api, login } from "services/api.mjs"; -import { usersService } from "services/usersApi.mjs"; +import { api, login } from "services/api.mjs"; // Verifique o caminho correto para 'api' e 'login' +import { usersService } from "services/usersApi.mjs"; // Verifique o caminho correto para 'usersApi.mjs' interface FlatUser { id: string; @@ -49,17 +48,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 +92,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 +119,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 +132,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 +153,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,8 +176,6 @@ export default function UsersPage() { }; const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage); - // --- Fim das Funções e Lógica de Navegação ADICIONADAS --- - return ( @@ -213,17 +199,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 +249,7 @@ export default function UsersPage() {
{/* Fim do Filtro e Itens por Página */} - {/* Tabela */} + {/* Tabela/Lista */}
{loading ? (
@@ -278,10 +264,10 @@ export default function UsersPage() {
) : ( <> - - + {/* Tabela para Telas Médias e Grandes */} +
+ - @@ -290,15 +276,8 @@ export default function UsersPage() { - {/* Usando currentItems para a paginação */} {currentItems.map((u) => ( - - + @@ -326,7 +305,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 +369,6 @@ export default function UsersPage() {
)} - {/* Fim da Paginação ATUALIZADA */} )}
@@ -401,7 +405,6 @@ export default function UsersPage() { Roles:{" "} {userDetails.roles?.join(", ")}
- {/* Melhoria na visualização das permissões no modal */}
Permissões:
    diff --git a/app/secretary/pacientes/page.tsx b/app/secretary/pacientes/page.tsx index 623a966..8d2b336 100644 --- a/app/secretary/pacientes/page.tsx +++ b/app/secretary/pacientes/page.tsx @@ -164,7 +164,7 @@ export default function PacientesPage() {
{/* Bloco de Filtros (Responsividade APLICADA) */} -
+
{/* Busca - Ocupa 100% no mobile, depois cresce */} @@ -173,14 +173,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 ? ( @@ -225,18 +227,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 */} @@ -265,7 +263,6 @@ export default function PacientesPage() { - {/* Aplicação das classes de visibilidade */} @@ -308,53 +305,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 @@ -370,7 +423,6 @@ export default function PacientesPage() { - {/* ... (AlertDialog de Detalhes) ... */} Detalhes do Paciente @@ -384,7 +436,7 @@ export default function PacientesPage() {
{patientDetails.error}
) : (
-
+

Nome Completo

{patientDetails.full_name}

@@ -420,7 +472,7 @@ export default function PacientesPage() {

Endereço

-
+

Rua

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

From c91cb5ccd3690ac76efa3c48d7d4873cdd196f29 Mon Sep 17 00:00:00 2001 From: GagoDuBroca Date: Mon, 10 Nov 2025 22:31:42 -0300 Subject: [PATCH 2/8] Novas Paginas Com Responsividade --- app/secretary/pacientes/[id]/editar/page.tsx | 96 ++++++++++++-------- app/secretary/pacientes/novo/page.tsx | 47 +++++----- 2 files changed, 82 insertions(+), 61 deletions(-) diff --git a/app/secretary/pacientes/[id]/editar/page.tsx b/app/secretary/pacientes/[id]/editar/page.tsx index 00f11fe..89d0f24 100644 --- a/app/secretary/pacientes/[id]/editar/page.tsx +++ b/app/secretary/pacientes/[id]/editar/page.tsx @@ -15,7 +15,7 @@ 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 { json } from "stream/consumers"; // Removido, pois não é usado e pode causar erro. export default function EditarPacientePage() { const router = useRouter(); @@ -81,6 +81,12 @@ export default function EditarPacientePage() { heightM?: string; bmi?: string; bloodType?: string; + // Adicionei os campos do convênio para o tipo FormData + convenio?: string; + plano?: string; + numeroMatricula?: string; + validadeCarteira?: string; + alergias?: string; }; @@ -133,6 +139,11 @@ export default function EditarPacientePage() { heightM: "", bmi: "", bloodType: "", + convenio: "", + plano: "", + numeroMatricula: "", + validadeCarteira: "", + alergias: "", }); const [isGuiaConvenio, setIsGuiaConvenio] = useState(false); @@ -141,7 +152,7 @@ export default function EditarPacientePage() { useEffect(() => { async function fetchPatient() { try { - const res = await patientsService.getById(patientId); + const res = await patientsService.getById(patientId); // Map API snake_case/nested to local camelCase form setFormData({ id: res[0]?.id ?? "", @@ -192,6 +203,12 @@ export default function EditarPacientePage() { heightM: res[0]?.height_m ? String(res[0].height_m) : "", bmi: res[0]?.bmi ? String(res[0].bmi) : "", bloodType: res[0]?.blood_type ?? "", + // Os campos de convênio e alergias não vêm da API, então os deixamos vazios ou com valores padrão + convenio: "", + plano: "", + numeroMatricula: "", + validadeCarteira: "", + alergias: "", }); } catch (e: any) { @@ -238,8 +255,8 @@ export default function EditarPacientePage() { router.push("/secretary/pacientes"); } catch (err: any) { console.error("Erro ao atualizar paciente:", err); - toast({ - title: "Erro", + toast({ + title: "Erro", description: err?.message || "Não foi possível atualizar o paciente", variant: "destructive" }); @@ -248,23 +265,25 @@ export default function EditarPacientePage() { return ( -
-
- - - -
-

Editar Paciente

-

Atualize as informações do paciente

+
{/* Adicionado padding responsivo e max-width */} +
{/* Ajustado para layout flexível */} +
+ + + +
+

Editar Paciente

+

Atualize as informações do paciente

+
- {/* Anexos Section */} -
+ {/* Anexos Section - Movido para fora do cabeçalho para melhor organização e responsividade */} +
{/* Ajustado largura */}

Anexos

-
+
+ {/* Dados Pessoais Section */}

Dados Pessoais

-
+
{/* Layout responsivo para os campos */} {/* Photo upload */} -
+
{/* Ocupa mais colunas em telas menores */} -
+
{/* Ajustado para layout flexível */}
{photoUrl ? ( // eslint-disable-next-line @next/next/no-img-element Foto do paciente ) : ( - Sem foto + Sem foto )}
-
+
{/* Botões empilhados em telas menores */}