From a6b116fb57c794c15ee96d59e64f1aa72f746e65 Mon Sep 17 00:00:00 2001 From: GagoDuBroca Date: Tue, 4 Nov 2025 23:10:34 -0300 Subject: [PATCH] Nova responsividade, novas Listagem de paginas --- app/doctor/disponibilidade/page.tsx | 81 ++-- app/doctor/medicos/page.tsx | 97 +++-- app/manager/home/page.tsx | 587 +++++++++++++++++----------- app/manager/usuario/page.tsx | 204 ++++++++-- app/secretary/pacientes/page.tsx | 55 +-- 5 files changed, 644 insertions(+), 380 deletions(-) diff --git a/app/doctor/disponibilidade/page.tsx b/app/doctor/disponibilidade/page.tsx index 127b93f..a5c2a51 100644 --- a/app/doctor/disponibilidade/page.tsx +++ b/app/doctor/disponibilidade/page.tsx @@ -94,43 +94,44 @@ export default function AvailabilityPage() {

Dados

-
-
- -
- - - - - - - -
+ {/* Ajuste de responsividade: removemos o grid para os dias da semana e focamos no div interno */} +
+ + {/* NOVO: Grid responsivo para os radio buttons dos dias da semana */} +
+ + + + + + +
-
+ {/* NOVO: Grid responsivo para os campos de horário e duração */} +
+ {/* A modalidade de consulta agora vai ocupar o espaço restante na linha ou ficar embaixo, dependendo do grid */}
@@ -168,14 +170,15 @@ export default function AvailabilityPage() {
-
+ {/* NOVO: Ajuste de responsividade para os botões */} +
- + - + -
@@ -183,4 +186,4 @@ export default function AvailabilityPage() {
); -} +} \ No newline at end of file diff --git a/app/doctor/medicos/page.tsx b/app/doctor/medicos/page.tsx index 7e315c5..9b2ec3f 100644 --- a/app/doctor/medicos/page.tsx +++ b/app/doctor/medicos/page.tsx @@ -54,7 +54,7 @@ export default function PacientesPage() { const formatDate = (dateString: string) => { if (!dateString) return ""; const date = new Date(dateString); - return new Intl.DateTimeFormat('pt-BR').format(date); + return new Intl.DateTimeFormat("pt-BR").format(date); }; const [itemsPerPage, setItemsPerPage] = useState(5); @@ -72,7 +72,7 @@ export default function PacientesPage() { 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 items = Array.isArray(json) ? json : Array.isArray(json?.data) ? json.data : []; const mapped = items.map((p: any) => ({ id: String(p.id ?? ""), @@ -107,36 +107,48 @@ export default function PacientesPage() { return ( -
+

Pacientes

-

Lista de pacientes vinculados

+

+ Lista de pacientes vinculados +

-
+
- +
- - - - - - - + + + + + + + {loading ? ( - ) : error ? ( - + ) : pacientes.length === 0 ? ( @@ -146,17 +158,32 @@ export default function PacientesPage() { ) : ( currentItems.map((p) => ( - - - - - - - - + + + + + + +
NomeTelefoneCidadeEstadoÚltimo atendimentoPróximo atendimentoAçõesNome + Telefone + + Cidade + + Estado + + Último atendimento + + Próximo atendimento + Ações
+ Carregando pacientes...
{`Erro: ${error}`}{`Erro: ${error}`}
{p.nome}{p.telefone}{p.cidade}{p.estado}{p.ultimoAtendimento}{p.proximoAtendimento} +
{p.nome} + {p.telefone} + + {p.cidade} + + {p.estado} + + {p.ultimoAtendimento} + + {p.proximoAtendimento} + - + handleOpenModal(p)}> @@ -175,11 +202,12 @@ export default function PacientesPage() { { - const newPacientes = pacientes.filter((pac) => pac.id !== p.id) - setPacientes(newPacientes) - alert(`Paciente ID: ${p.id} excluído`) + const newPacientes = pacientes.filter((pac) => pac.id !== p.id); + setPacientes(newPacientes); + alert(`Paciente ID: ${p.id} excluído`); }} - className="text-red-600"> + className="text-red-600" + > Excluir @@ -192,12 +220,18 @@ export default function PacientesPage() {
-
+ + {/* Paginação Responsiva */} +
{Array.from({ length: Math.ceil(pacientes.length / itemsPerPage) }, (_, i) => ( @@ -205,6 +239,7 @@ export default function PacientesPage() {
+ ); -} \ No newline at end of file +} diff --git a/app/manager/home/page.tsx b/app/manager/home/page.tsx index abbc858..86e4750 100644 --- a/app/manager/home/page.tsx +++ b/app/manager/home/page.tsx @@ -1,13 +1,33 @@ "use client"; -import React, { useEffect, useState, useCallback } from "react" +import React, { useEffect, useState, useCallback } 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, MoreVertical, 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, + MoreVertical, + Loader2, +} from "lucide-react"; import { AlertDialog, AlertDialogAction, @@ -17,36 +37,25 @@ 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; - specialty: string; - crm: string; - phone_mobile: string | null; - city: string | null; - state: string | null; - + id: number; + full_name: string; + specialty: string; + crm: string; + phone_mobile: string | null; + city: string | null; + state: string | null; } - interface DoctorDetails { nome: string; crm: string; especialidade: string; - - contato: { - celular?: string; - telefone1?: string; - } - endereco: { - cidade?: string; - estado?: string; - } + contato: { celular?: string; telefone1?: string }; + endereco: { cidade?: string; estado?: string }; convenio?: string; vip?: boolean; status?: string; @@ -58,80 +67,86 @@ interface DoctorDetails { export default function DoctorsPage() { const router = useRouter(); - const [doctors, setDoctors] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); - const [doctorDetails, setDoctorDetails] = useState(null); + const [doctorDetails, setDoctorDetails] = useState( + null + ); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [doctorToDeleteId, setDoctorToDeleteId] = useState(null); - - - + // --- Lógica de Paginação --- + const [itemsPerPage, setItemsPerPage] = useState(10); + const [currentPage, setCurrentPage] = useState(1); + + // Cálculo dos itens a serem exibidos na página atual + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = doctors.slice(indexOfFirstItem, indexOfLastItem); + + // Função para mudar de página + const paginate = (pageNumber: number) => setCurrentPage(pageNumber); + + // 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 + }; + // --- Fim da Lógica de Paginação --- + const fetchDoctors = useCallback(async () => { setLoading(true); setError(null); try { - const data: Doctor[] = await doctorsService.list(); - setDoctors(data || []); + setDoctors(data || []); + setCurrentPage(1); // Resetar para a primeira página ao carregar novos dados } catch (e: any) { console.error("Erro ao carregar lista de médicos:", e); - setError("Não foi possível carregar a lista de médicos. Verifique a conexão com a API."); + setError( + "Não foi possível carregar a lista de médicos. Verifique a conexão com a API." + ); setDoctors([]); } finally { setLoading(false); } }, []); - useEffect(() => { fetchDoctors(); }, [fetchDoctors]); - const openDetailsDialog = async (doctor: Doctor) => { setDetailsDialogOpen(true); - setDoctorDetails({ - nome: doctor.full_name, - crm: doctor.crm, - especialidade: doctor.specialty, - contato: { - celular: doctor.phone_mobile ?? undefined, - telefone1: undefined - }, - endereco: { - cidade: doctor.city ?? undefined, - estado: doctor.state ?? undefined, - }, - - convenio: "Particular", - vip: false, - status: "Ativo", - ultimo_atendimento: "N/A", - proximo_atendimento: "N/A", + nome: doctor.full_name, + crm: doctor.crm, + especialidade: doctor.specialty, + contato: { celular: doctor.phone_mobile ?? undefined }, + endereco: { + cidade: doctor.city ?? undefined, + estado: doctor.state ?? undefined, + }, + convenio: "Particular", + vip: false, + status: "Ativo", + ultimo_atendimento: "N/A", + proximo_atendimento: "N/A", }); }; - const handleDelete = async () => { if (doctorToDeleteId === null) return; - setLoading(true); try { await doctorsService.delete(doctorToDeleteId); - - console.log(`Médico com ID ${doctorToDeleteId} excluído com sucesso!`); - setDeleteDialogOpen(false); setDoctorToDeleteId(null); - await fetchDoctors(); + await fetchDoctors(); } catch (e) { console.error("Erro ao excluir:", e); - alert("Erro ao excluir médico."); } finally { setLoading(false); @@ -142,192 +157,304 @@ export default function DoctorsPage() { setDoctorToDeleteId(doctorId); setDeleteDialogOpen(true); }; - const handleEdit = (doctorId: number) => { - router.push(`/manager/home/${doctorId}/editar`); }; - return ( -
-
-
-

Médicos Cadastrados

-

Gerencie todos os profissionais de saúde.

+
+ {/* Cabeçalho */} +
+
+

+ Médicos Cadastrados +

+

+ Gerencie todos os profissionais de saúde. +

+
+ + +
- - - -
- -
- - - -
+ {/* Filtros e Itens por Página */} +
+ + + + {/* Select de Itens por Página Adicionado */} + +
- -
- {loading ? ( + {/* Tabela */} +
+ {loading ? (
- - Carregando médicos... + + Carregando médicos...
- ) : error ? ( -
- {error} -
- ) : doctors.length === 0 ? ( + ) : error ? ( +
{error}
+ ) : doctors.length === 0 ? (
- Nenhum médico cadastrado. Adicione um novo. + Nenhum médico cadastrado.{" "} + + Adicione um novo + + .
- ) : ( -
- - - - - - - - - - - - - {doctors.map((doctor) => ( - - - - - - - + ) : ( +
+
NomeCRMEspecialidadeCelularCidade/EstadoAções
{doctor.full_name}{doctor.crm}{doctor.specialty}{doctor.phone_mobile || "N/A"} - {(doctor.city || doctor.state) ? `${doctor.city || ''}${doctor.city && doctor.state ? '/' : ''}${doctor.state || ''}` : "N/A"} - - -
- - - - - - - - - - - - - - - - Agendar Consulta - - - -
-
+ + + + + + + + - ))} - -
+ Nome + + CRM + + Especialidade + + Celular + + Cidade/Estado + + Ações +
+ + + {/* Usando currentItems para a paginação */} + {currentItems.map((doctor) => ( + + + {doctor.full_name} + + + {doctor.crm} + + + {doctor.specialty} + + + {doctor.phone_mobile || "N/A"} + + + {doctor.city || doctor.state + ? `${doctor.city || ""}${ + doctor.city && doctor.state ? "/" : "" + }${doctor.state || ""}` + : "N/A"} + + +
+ + + + + + + + + + + Agendar Consulta + + + +
+ + + ))} + + +
+ )} +
+ + {/* Paginação */} + {doctors.length > itemsPerPage && ( +
+ {Array.from({ length: Math.ceil(doctors.length / itemsPerPage) }, (_, i) => ( + + ))}
)} -
- - - - - - Confirma a exclusão? - - Esta ação é irreversível e excluirá permanentemente o registro deste médico. - - - - Cancelar - - {loading ? ( - - ) : null} - Excluir - - - - - - - - - {doctorDetails?.nome} - - {doctorDetails && ( -
-

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'}`}
+ {/* Dialogs */} + + + + Confirma a exclusão? + + Esta ação é irreversível e excluirá permanentemente o registro + deste médico. + + + + Cancelar + + {loading && ( + + )} + Excluir + + + + + + + + + + {doctorDetails?.nome} + + + {doctorDetails && ( +
+

+ 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" + }`} +
+
+ +

+ 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"} +
+
- -

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'}
-
-
- )} - {doctorDetails === null && !loading && ( -
Detalhes não disponíveis.
- )} - - - - Fechar - - - -
+ )} +
+
+ + Fechar + +
+
+
); -} +} \ No newline at end of file diff --git a/app/manager/usuario/page.tsx b/app/manager/usuario/page.tsx index 660db2a..876fbdd 100644 --- a/app/manager/usuario/page.tsx +++ b/app/manager/usuario/page.tsx @@ -46,20 +46,33 @@ export default function UsersPage() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); - const [userDetails, setUserDetails] = useState(null); + const [userDetails, setUserDetails] = useState( + null + ); const [selectedRole, setSelectedRole] = useState(""); + // --- Lógica de Paginação ADICIONADA --- + 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 + }; + // --- Fim da Lógica de Paginação ADICIONADA --- + const fetchUsers = useCallback(async () => { setLoading(true); setError(null); try { - // 1) pega roles const rolesData: any[] = await usersService.list_roles(); - // Garante que rolesData é array const rolesArray = Array.isArray(rolesData) ? rolesData : []; - // 2) pega todos os profiles de uma vez (para evitar muitos requests) - const profilesData: any[] = await api.get(`/rest/v1/profiles?select=id,full_name,email,phone`); + const profilesData: any[] = await api.get( + `/rest/v1/profiles?select=id,full_name,email,phone` + ); + const profilesById = new Map(); if (Array.isArray(profilesData)) { for (const p of profilesData) { @@ -67,7 +80,6 @@ export default function UsersPage() { } } - // 3) mapear roles -> flat users, usando ID específico de cada item const mapped: FlatUser[] = rolesArray.map((roleItem) => { const uid = roleItem.user_id; const profile = profilesById.get(uid); @@ -82,6 +94,7 @@ export default function UsersPage() { }); setUsers(mapped); + setCurrentPage(1); // Resetar a página após carregar console.log("[fetchUsers] mapped count:", mapped.length); } catch (err: any) { console.error("Erro ao buscar usuários:", err); @@ -95,7 +108,7 @@ export default function UsersPage() { useEffect(() => { const init = async () => { try { - await login(); // garante token + await login(); } catch (e) { console.warn("login falhou no init:", e); } @@ -115,7 +128,6 @@ export default function UsersPage() { setUserDetails(data); } catch (err: any) { console.error("Erro ao carregar detalhes:", err); - // fallback com dados já conhecidos setUserDetails({ user: { id: flatUser.user_id, email: flatUser.email }, profile: { full_name: flatUser.full_name, phone: flatUser.phone }, @@ -125,26 +137,48 @@ export default function UsersPage() { } }; + // 1. Filtragem const filteredUsers = - selectedRole && selectedRole !== "all" ? users.filter((u) => u.role === selectedRole) : users; + 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 pageNumbers = []; + for ( + let i = 1; + i <= Math.ceil(filteredUsers.length / itemsPerPage); + i++ + ) { + pageNumbers.push(i); + } return ( -
-
+
+ {/* Header */} +

Usuários

Gerencie usuários.

- -
-
+ {/* Filtro e Itens por Página ADICIONADO */} +
+ {/* Select de Filtro por Papel */} + {/* Select de Itens por Página ADICIONADO */} +
+ {/* Fim do Filtro e Itens por Página ADICIONADO */} -
+ {/* Tabela */} +
{loading ? (
@@ -170,31 +220,62 @@ export default function UsersPage() {
{error}
) : filteredUsers.length === 0 ? (
- Nenhum usuário encontrado. + Nenhum usuário encontrado com os filtros aplicados.
) : ( -
+ <> - + - - - - - - + + + + + + - {filteredUsers.map((u) => ( - - - - - - + {/* Usando currentItems para a paginação */} + {currentItems.map((u) => ( + + + + + + @@ -202,14 +283,37 @@ export default function UsersPage() { ))}
IDNomeE-mailTelefoneCargoAções + ID + + Nome + + E-mail + + Telefone + + Cargo + + Ações +
{u.id}{u.full_name}{u.email}{u.phone}{u.role}
+ {u.id} + + {u.full_name} + + {u.email} + + {u.phone} + + {u.role} + -
-
+ + {/* Paginação ADICIONADA */} + {pageNumbers.length > 1 && ( +
+ {pageNumbers.map((number) => ( + + ))} +
+ )} + {/* Fim da Paginação ADICIONADA */} + )}
+ {/* Modal de Detalhes */} - {userDetails?.profile?.full_name || "Detalhes do Usuário"} + + {userDetails?.profile?.full_name || "Detalhes do Usuário"} + {!userDetails ? (
@@ -218,15 +322,33 @@ export default function UsersPage() {
) : (
-
ID: {userDetails.user.id}
-
E-mail: {userDetails.user.email}
-
Nome completo: {userDetails.profile.full_name}
-
Telefone: {userDetails.profile.phone}
-
Roles: {userDetails.roles?.join(", ")}
+
+ ID: {userDetails.user.id} +
+
+ E-mail: {userDetails.user.email} +
+
+ Nome completo:{" "} + {userDetails.profile.full_name} +
+
+ Telefone: {userDetails.profile.phone} +
+
+ Roles:{" "} + {userDetails.roles?.join(", ")} +
Permissões:
    - {Object.entries(userDetails.permissions || {}).map(([k,v]) =>
  • {k}: {v ? "Sim" : "Não"}
  • )} + {Object.entries( + userDetails.permissions || {} + ).map(([k, v]) => ( +
  • + {k}: {v ? "Sim" : "Não"} +
  • + ))}
@@ -241,4 +363,4 @@ export default function UsersPage() {
); -} +} \ No newline at end of file diff --git a/app/secretary/pacientes/page.tsx b/app/secretary/pacientes/page.tsx index 3715261..e7f73af 100644 --- a/app/secretary/pacientes/page.tsx +++ b/app/secretary/pacientes/page.tsx @@ -139,12 +139,13 @@ export default function PacientesPage() {
-
+ {/* Bloco de Filtros: Corrigido o uso de classes para responsividade e removida a duplicação dos filtros VIP e Aniversariantes */} +
{/* Convênio */} -
- Convênio +
+ Convênio
-
- VIP + {/* VIP */} +
+ VIP -
-
- Aniversariantes - -
- -
- VIP -
-
- Aniversariantes + {/* Aniversariantes */} +
+ Aniversariantes