Nova responsividade, novas Listagem de paginas

This commit is contained in:
GagoDuBroca 2025-11-04 23:10:34 -03:00
parent 2a015a7f63
commit a6b116fb57
5 changed files with 644 additions and 380 deletions

View File

@ -94,43 +94,44 @@ export default function AvailabilityPage() {
<h2 className="text-lg font-semibold text-gray-900 mb-6">Dados </h2> <h2 className="text-lg font-semibold text-gray-900 mb-6">Dados </h2>
<div className="space-y-6"> <div className="space-y-6">
<div className="grid md:grid-cols-3 gap-4"> {/* Ajuste de responsividade: removemos o grid para os dias da semana e focamos no div interno */}
<div> <div>
<Label className="text-sm font-medium text-gray-700">Dia Da Semana</Label> <Label className="text-sm font-medium text-gray-700">Dia Da Semana</Label>
<div className="flex gap-4 mt-2 flex-nowrap"> {/* NOVO: Grid responsivo para os radio buttons dos dias da semana */}
<label className="flex items-center gap-2"> <div className="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-7 gap-x-2 gap-y-1 mt-2">
<label className="flex items-center gap-1">
<input type="radio" name="weekday" value="monday" className="text-blue-600" /> <input type="radio" name="weekday" value="monday" className="text-blue-600" />
<span className="whitespace-nowrap text-sm">Segunda-Feira</span> <span className="whitespace-nowrap text-sm">Segunda</span>
</label> </label>
<label className="flex items-center gap-2"> <label className="flex items-center gap-1">
<input type="radio" name="weekday" value="tuesday" className="text-blue-600" /> <input type="radio" name="weekday" value="tuesday" className="text-blue-600" />
<span className="whitespace-nowrap text-sm">Terça-Feira</span> <span className="whitespace-nowrap text-sm">Terça</span>
</label> </label>
<label className="flex items-center gap-2"> <label className="flex items-center gap-1">
<input type="radio" name="weekday" value="wednesday" className="text-blue-600" /> <input type="radio" name="weekday" value="wednesday" className="text-blue-600" />
<span className="whitespace-nowrap text-sm">Quarta-Feira</span> <span className="whitespace-nowrap text-sm">Quarta</span>
</label> </label>
<label className="flex items-center gap-2"> <label className="flex items-center gap-1">
<input type="radio" name="weekday" value="thursday" className="text-blue-600" /> <input type="radio" name="weekday" value="thursday" className="text-blue-600" />
<span className="whitespace-nowrap text-sm">Quinta-Feira</span> <span className="whitespace-nowrap text-sm">Quinta</span>
</label> </label>
<label className="flex items-center gap-2"> <label className="flex items-center gap-1">
<input type="radio" name="weekday" value="friday" className="text-blue-600" /> <input type="radio" name="weekday" value="friday" className="text-blue-600" />
<span className="whitespace-nowrap text-sm">Sexta-Feira</span> <span className="whitespace-nowrap text-sm">Sexta</span>
</label> </label>
<label className="flex items-center gap-2"> <label className="flex items-center gap-1">
<input type="radio" name="weekday" value="saturday" className="text-blue-600" /> <input type="radio" name="weekday" value="saturday" className="text-blue-600" />
<span className="whitespace-nowrap text-sm">Sabado</span> <span className="whitespace-nowrap text-sm">Sábado</span>
</label> </label>
<label className="flex items-center gap-2"> <label className="flex items-center gap-1">
<input type="radio" name="weekday" value="sunday" className="text-blue-600" /> <input type="radio" name="weekday" value="sunday" className="text-blue-600" />
<span className="whitespace-nowrap text-sm">Domingo</span> <span className="whitespace-nowrap text-sm">Domingo</span>
</label> </label>
</div> </div>
</div> </div>
</div>
<div className="grid md:grid-cols-5 gap-6"> {/* NOVO: Grid responsivo para os campos de horário e duração */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
<div> <div>
<Label htmlFor="horarioEntrada" className="text-sm font-medium text-gray-700"> <Label htmlFor="horarioEntrada" className="text-sm font-medium text-gray-700">
Horario De Entrada Horario De Entrada
@ -149,6 +150,7 @@ export default function AvailabilityPage() {
</Label> </Label>
<Input type="number" id="duracaoConsulta" name="duracaoConsulta" required className="mt-1" /> <Input type="number" id="duracaoConsulta" name="duracaoConsulta" required className="mt-1" />
</div> </div>
{/* A modalidade de consulta agora vai ocupar o espaço restante na linha ou ficar embaixo, dependendo do grid */}
</div> </div>
<div> <div>
@ -168,14 +170,15 @@ export default function AvailabilityPage() {
</div> </div>
</div> </div>
<div className="flex justify-end gap-4"> {/* NOVO: Ajuste de responsividade para os botões */}
<div className="flex flex-col-reverse sm:flex-row sm:justify-end gap-4">
<Link href="/doctor/disponibilidade/excecoes"> <Link href="/doctor/disponibilidade/excecoes">
<Button variant="outline">Adicionar Exceção</Button> <Button variant="outline" className="w-full sm:w-auto">Adicionar Exceção</Button>
</Link> </Link>
<Link href="/doctor/dashboard"> <Link href="/doctor/dashboard">
<Button variant="outline">Cancelar</Button> <Button variant="outline" className="w-full sm:w-auto">Cancelar</Button>
</Link> </Link>
<Button type="submit" className="bg-green-600 hover:bg-green-700"> <Button type="submit" className="bg-green-600 hover:bg-green-700 w-full sm:w-auto">
Salvar Disponibilidade Salvar Disponibilidade
</Button> </Button>
</div> </div>

View File

@ -54,7 +54,7 @@ export default function PacientesPage() {
const formatDate = (dateString: string) => { const formatDate = (dateString: string) => {
if (!dateString) return ""; if (!dateString) return "";
const date = new Date(dateString); 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); const [itemsPerPage, setItemsPerPage] = useState(5);
@ -72,7 +72,7 @@ export default function PacientesPage() {
setLoading(true); setLoading(true);
setError(null); setError(null);
const json = await api.get("/rest/v1/patients"); 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) => ({ const mapped = items.map((p: any) => ({
id: String(p.id ?? ""), id: String(p.id ?? ""),
@ -107,36 +107,48 @@ export default function PacientesPage() {
return ( return (
<DoctorLayout> <DoctorLayout>
<div className="space-y-6"> <div className="space-y-6 px-2 sm:px-4 md:px-6">
<div> <div>
<h1 className="text-2xl font-bold text-foreground">Pacientes</h1> <h1 className="text-2xl font-bold text-foreground">Pacientes</h1>
<p className="text-muted-foreground">Lista de pacientes vinculados</p> <p className="text-muted-foreground text-sm sm:text-base">
Lista de pacientes vinculados
</p>
</div> </div>
<div className="bg-card rounded-lg border border-border"> <div className="bg-card rounded-lg border border-border overflow-hidden">
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full"> <table className="min-w-[600px] w-full">
<thead className="bg-muted border-b border-border"> <thead className="bg-muted border-b border-border">
<tr> <tr>
<th className="text-left p-4 font-medium text-foreground">Nome</th> <th className="text-left p-3 sm:p-4 font-medium text-foreground">Nome</th>
<th className="text-left p-4 font-medium text-foreground">Telefone</th> <th className="text-left p-3 sm:p-4 font-medium text-foreground hidden md:table-cell">
<th className="text-left p-4 font-medium text-foreground">Cidade</th> Telefone
<th className="text-left p-4 font-medium text-foreground">Estado</th> </th>
<th className="text-left p-4 font-medium text-foreground">Último atendimento</th> <th className="text-left p-3 sm:p-4 font-medium text-foreground hidden lg:table-cell">
<th className="text-left p-4 font-medium text-foreground">Próximo atendimento</th> Cidade
<th className="text-left p-4 font-medium text-foreground">Ações</th> </th>
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden lg:table-cell">
Estado
</th>
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden xl:table-cell">
Último atendimento
</th>
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden xl:table-cell">
Próximo atendimento
</th>
<th className="text-left p-3 sm:p-4 font-medium text-foreground">Ações</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{loading ? ( {loading ? (
<tr> <tr>
<td colSpan={7} className="p-6 text-muted-foreground"> <td colSpan={7} className="p-6 text-muted-foreground text-center">
Carregando pacientes... Carregando pacientes...
</td> </td>
</tr> </tr>
) : error ? ( ) : error ? (
<tr> <tr>
<td colSpan={7} className="p-6 text-red-600">{`Erro: ${error}`}</td> <td colSpan={7} className="p-6 text-red-600 text-center">{`Erro: ${error}`}</td>
</tr> </tr>
) : pacientes.length === 0 ? ( ) : pacientes.length === 0 ? (
<tr> <tr>
@ -146,17 +158,32 @@ export default function PacientesPage() {
</tr> </tr>
) : ( ) : (
currentItems.map((p) => ( currentItems.map((p) => (
<tr key={p.id} className="border-b border-border hover:bg-accent"> <tr
<td className="p-4">{p.nome}</td> key={p.id}
<td className="p-4 text-muted-foreground">{p.telefone}</td> className="border-b border-border hover:bg-accent/40 transition-colors"
<td className="p-4 text-muted-foreground">{p.cidade}</td> >
<td className="p-4 text-muted-foreground">{p.estado}</td> <td className="p-3 sm:p-4">{p.nome}</td>
<td className="p-4 text-muted-foreground">{p.ultimoAtendimento}</td> <td className="p-3 sm:p-4 text-muted-foreground hidden md:table-cell">
<td className="p-4 text-muted-foreground">{p.proximoAtendimento}</td> {p.telefone}
<td className="p-4"> </td>
<td className="p-3 sm:p-4 text-muted-foreground hidden lg:table-cell">
{p.cidade}
</td>
<td className="p-3 sm:p-4 text-muted-foreground hidden lg:table-cell">
{p.estado}
</td>
<td className="p-3 sm:p-4 text-muted-foreground hidden xl:table-cell">
{p.ultimoAtendimento}
</td>
<td className="p-3 sm:p-4 text-muted-foreground hidden xl:table-cell">
{p.proximoAtendimento}
</td>
<td className="p-3 sm:p-4">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<button className="text-primary hover:underline">Ações</button> <button className="text-primary hover:underline text-sm sm:text-base">
Ações
</button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleOpenModal(p)}> <DropdownMenuItem onClick={() => handleOpenModal(p)}>
@ -175,11 +202,12 @@ export default function PacientesPage() {
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
const newPacientes = pacientes.filter((pac) => pac.id !== p.id) const newPacientes = pacientes.filter((pac) => pac.id !== p.id);
setPacientes(newPacientes) setPacientes(newPacientes);
alert(`Paciente ID: ${p.id} excluído`) alert(`Paciente ID: ${p.id} excluído`);
}} }}
className="text-red-600"> className="text-red-600"
>
<Trash2 className="w-4 h-4 mr-2" /> <Trash2 className="w-4 h-4 mr-2" />
Excluir Excluir
</DropdownMenuItem> </DropdownMenuItem>
@ -192,12 +220,18 @@ export default function PacientesPage() {
</tbody> </tbody>
</table> </table>
</div> </div>
<div className="flex justify-center space-x-2 mt-4 p-4">
{/* Paginação Responsiva */}
<div className="flex flex-wrap justify-center items-center gap-2 mt-4 p-4">
{Array.from({ length: Math.ceil(pacientes.length / itemsPerPage) }, (_, i) => ( {Array.from({ length: Math.ceil(pacientes.length / itemsPerPage) }, (_, i) => (
<button <button
key={i} key={i}
onClick={() => paginate(i + 1)} onClick={() => paginate(i + 1)}
className={`px-4 py-2 rounded-md ${currentPage === i + 1 ? 'bg-primary text-primary-foreground' : 'bg-secondary text-secondary-foreground'}`} className={`px-3 py-2 rounded-md text-sm sm:text-base transition-colors ${
currentPage === i + 1
? "bg-primary text-primary-foreground"
: "bg-secondary text-secondary-foreground hover:bg-secondary/80"
}`}
> >
{i + 1} {i + 1}
</button> </button>
@ -205,6 +239,7 @@ export default function PacientesPage() {
</div> </div>
</div> </div>
</div> </div>
<PatientDetailsModal <PatientDetailsModal
patient={selectedPatient} patient={selectedPatient}
isOpen={isModalOpen} isOpen={isModalOpen}

View File

@ -1,13 +1,33 @@
"use client"; "use client";
import React, { useEffect, useState, useCallback } from "react" import React, { useEffect, useState, useCallback } from "react";
import ManagerLayout from "@/components/manager-layout"; import ManagerLayout from "@/components/manager-layout";
import Link from "next/link" import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import {
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" DropdownMenu,
import { Plus, Edit, Trash2, Eye, Calendar, Filter, MoreVertical, Loader2 } from "lucide-react" 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 { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
@ -17,11 +37,9 @@ import {
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
} from "@/components/ui/alert-dialog" } from "@/components/ui/alert-dialog";
import { doctorsService } from "services/doctorsApi.mjs"; import { doctorsService } from "services/doctorsApi.mjs";
interface Doctor { interface Doctor {
id: number; id: number;
full_name: string; full_name: string;
@ -30,23 +48,14 @@ interface Doctor {
phone_mobile: string | null; phone_mobile: string | null;
city: string | null; city: string | null;
state: string | null; state: string | null;
} }
interface DoctorDetails { interface DoctorDetails {
nome: string; nome: string;
crm: string; crm: string;
especialidade: string; especialidade: string;
contato: { celular?: string; telefone1?: string };
contato: { endereco: { cidade?: string; estado?: string };
celular?: string;
telefone1?: string;
}
endereco: {
cidade?: string;
estado?: string;
}
convenio?: string; convenio?: string;
vip?: boolean; vip?: boolean;
status?: string; status?: string;
@ -58,56 +67,68 @@ interface DoctorDetails {
export default function DoctorsPage() { export default function DoctorsPage() {
const router = useRouter(); const router = useRouter();
const [doctors, setDoctors] = useState<Doctor[]>([]); const [doctors, setDoctors] = useState<Doctor[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
const [doctorDetails, setDoctorDetails] = useState<DoctorDetails | null>(null); const [doctorDetails, setDoctorDetails] = useState<DoctorDetails | null>(
null
);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [doctorToDeleteId, setDoctorToDeleteId] = useState<number | null>(null); const [doctorToDeleteId, setDoctorToDeleteId] = useState<number | null>(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 () => { const fetchDoctors = useCallback(async () => {
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
const data: Doctor[] = await doctorsService.list(); 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) { } catch (e: any) {
console.error("Erro ao carregar lista de médicos:", e); 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([]); setDoctors([]);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, []);
useEffect(() => { useEffect(() => {
fetchDoctors(); fetchDoctors();
}, [fetchDoctors]); }, [fetchDoctors]);
const openDetailsDialog = async (doctor: Doctor) => { const openDetailsDialog = async (doctor: Doctor) => {
setDetailsDialogOpen(true); setDetailsDialogOpen(true);
setDoctorDetails({ setDoctorDetails({
nome: doctor.full_name, nome: doctor.full_name,
crm: doctor.crm, crm: doctor.crm,
especialidade: doctor.specialty, especialidade: doctor.specialty,
contato: { contato: { celular: doctor.phone_mobile ?? undefined },
celular: doctor.phone_mobile ?? undefined,
telefone1: undefined
},
endereco: { endereco: {
cidade: doctor.city ?? undefined, cidade: doctor.city ?? undefined,
estado: doctor.state ?? undefined, estado: doctor.state ?? undefined,
}, },
convenio: "Particular", convenio: "Particular",
vip: false, vip: false,
status: "Ativo", status: "Ativo",
@ -116,22 +137,16 @@ export default function DoctorsPage() {
}); });
}; };
const handleDelete = async () => { const handleDelete = async () => {
if (doctorToDeleteId === null) return; if (doctorToDeleteId === null) return;
setLoading(true); setLoading(true);
try { try {
await doctorsService.delete(doctorToDeleteId); await doctorsService.delete(doctorToDeleteId);
console.log(`Médico com ID ${doctorToDeleteId} excluído com sucesso!`);
setDeleteDialogOpen(false); setDeleteDialogOpen(false);
setDoctorToDeleteId(null); setDoctorToDeleteId(null);
await fetchDoctors(); await fetchDoctors();
} catch (e) { } catch (e) {
console.error("Erro ao excluir:", e); console.error("Erro ao excluir:", e);
alert("Erro ao excluir médico."); alert("Erro ao excluir médico.");
} finally { } finally {
setLoading(false); setLoading(false);
@ -143,34 +158,36 @@ export default function DoctorsPage() {
setDeleteDialogOpen(true); setDeleteDialogOpen(true);
}; };
const handleEdit = (doctorId: number) => { const handleEdit = (doctorId: number) => {
router.push(`/manager/home/${doctorId}/editar`); router.push(`/manager/home/${doctorId}/editar`);
}; };
return ( return (
<ManagerLayout> <ManagerLayout>
<div className="space-y-6"> <div className="space-y-6 px-2 sm:px-4 md:px-6">
<div className="flex items-center justify-between"> {/* Cabeçalho */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">Médicos Cadastrados</h1> <h1 className="text-2xl font-bold text-gray-900">
<p className="text-sm text-gray-500">Gerencie todos os profissionais de saúde.</p> Médicos Cadastrados
</h1>
<p className="text-sm text-gray-500">
Gerencie todos os profissionais de saúde.
</p>
</div> </div>
<Link href="/manager/home/novo"> <Link href="/manager/home/novo" className="w-full sm:w-auto">
<Button className="bg-green-600 hover:bg-green-700"> <Button className="w-full sm:w-auto bg-green-600 hover:bg-green-700">
<Plus className="w-4 h-4 mr-2" /> <Plus className="w-4 h-4 mr-2" />
Adicionar Novo Adicionar Novo
</Button> </Button>
</Link> </Link>
</div> </div>
{/* Filtros e Itens por Página */}
<div className="flex items-center space-x-4 bg-white p-4 rounded-lg border border-gray-200"> <div className="flex flex-wrap items-center gap-3 bg-white p-3 sm:p-4 rounded-lg border border-gray-200">
<Filter className="w-5 h-5 text-gray-400" /> <Filter className="w-5 h-5 text-gray-400" />
<Select> <Select>
<SelectTrigger className="w-[180px]"> <SelectTrigger className="w-[160px] sm:w-[180px]">
<SelectValue placeholder="Especialidade" /> <SelectValue placeholder="Especialidade" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -180,7 +197,7 @@ export default function DoctorsPage() {
</SelectContent> </SelectContent>
</Select> </Select>
<Select> <Select>
<SelectTrigger className="w-[180px]"> <SelectTrigger className="w-[160px] sm:w-[180px]">
<SelectValue placeholder="Status" /> <SelectValue placeholder="Status" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -189,9 +206,23 @@ export default function DoctorsPage() {
<SelectItem value="inativo">Inativo</SelectItem> <SelectItem value="inativo">Inativo</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
{/* Select de Itens por Página Adicionado */}
<Select
onValueChange={handleItemsPerPageChange}
defaultValue={String(itemsPerPage)}
>
<SelectTrigger className="w-[140px]">
<SelectValue placeholder="Itens por pág." />
</SelectTrigger>
<SelectContent>
<SelectItem value="5">5 por página</SelectItem>
<SelectItem value="10">10 por página</SelectItem>
<SelectItem value="20">20 por página</SelectItem>
</SelectContent>
</Select>
</div> </div>
{/* Tabela */}
<div className="bg-white rounded-lg border border-gray-200 shadow-md overflow-hidden"> <div className="bg-white rounded-lg border border-gray-200 shadow-md overflow-hidden">
{loading ? ( {loading ? (
<div className="p-8 text-center text-gray-500"> <div className="p-8 text-center text-gray-500">
@ -199,57 +230,99 @@ export default function DoctorsPage() {
Carregando médicos... Carregando médicos...
</div> </div>
) : error ? ( ) : error ? (
<div className="p-8 text-center text-red-600"> <div className="p-8 text-center text-red-600">{error}</div>
{error}
</div>
) : doctors.length === 0 ? ( ) : doctors.length === 0 ? (
<div className="p-8 text-center text-gray-500"> <div className="p-8 text-center text-gray-500">
Nenhum médico cadastrado. <Link href="/manager/home/novo" className="text-green-600 hover:underline">Adicione um novo</Link>. Nenhum médico cadastrado.{" "}
<Link
href="/manager/home/novo"
className="text-green-600 hover:underline"
>
Adicione um novo
</Link>
.
</div> </div>
) : ( ) : (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200"> <table className="min-w-[700px] w-full divide-y divide-gray-200 text-sm sm:text-base">
<thead className="bg-gray-50"> <thead className="bg-gray-50">
<tr> <tr>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nome</th> <th className="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">CRM</th> Nome
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Especialidade</th> </th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Celular</th> <th className="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider hidden sm:table-cell">
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cidade/Estado</th> CRM
<th scope="col" className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Ações</th> </th>
<th className="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider hidden md:table-cell">
Especialidade
</th>
<th className="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider hidden lg:table-cell">
Celular
</th>
<th className="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider hidden xl:table-cell">
Cidade/Estado
</th>
<th className="px-4 py-3 text-right font-medium text-gray-500 uppercase tracking-wider">
Ações
</th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
{doctors.map((doctor) => ( {/* Usando currentItems para a paginação */}
<tr key={doctor.id} className="hover:bg-gray-50"> {currentItems.map((doctor) => (
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{doctor.full_name}</td> <tr key={doctor.id} className="hover:bg-gray-50 transition">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{doctor.crm}</td> <td className="px-4 py-3 font-medium text-gray-900">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{doctor.specialty}</td> {doctor.full_name}
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{doctor.phone_mobile || "N/A"}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{(doctor.city || doctor.state) ? `${doctor.city || ''}${doctor.city && doctor.state ? '/' : ''}${doctor.state || ''}` : "N/A"}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <td className="px-4 py-3 text-gray-500 hidden sm:table-cell">
{doctor.crm}
<div className="flex justify-end space-x-1"> </td>
<td className="px-4 py-3 text-gray-500 hidden md:table-cell">
<Button variant="outline" size="icon" onClick={() => openDetailsDialog(doctor)} title="Visualizar Detalhes"> {doctor.specialty}
</td>
<td className="px-4 py-3 text-gray-500 hidden lg:table-cell">
{doctor.phone_mobile || "N/A"}
</td>
<td className="px-4 py-3 text-gray-500 hidden xl:table-cell">
{doctor.city || doctor.state
? `${doctor.city || ""}${
doctor.city && doctor.state ? "/" : ""
}${doctor.state || ""}`
: "N/A"}
</td>
<td className="px-4 py-3 text-right">
<div className="flex justify-end gap-2 flex-wrap sm:flex-nowrap">
<Button
variant="outline"
size="icon"
onClick={() => openDetailsDialog(doctor)}
title="Visualizar Detalhes"
>
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
</Button> </Button>
<Button
<Button variant="outline" size="icon" onClick={() => handleEdit(doctor.id)} title="Editar"> variant="outline"
size="icon"
onClick={() => handleEdit(doctor.id)}
title="Editar"
>
<Edit className="h-4 w-4 text-blue-600" /> <Edit className="h-4 w-4 text-blue-600" />
</Button> </Button>
<Button
<Button variant="outline" size="icon" onClick={() => openDeleteDialog(doctor.id)} title="Excluir"> variant="outline"
size="icon"
onClick={() => openDeleteDialog(doctor.id)}
title="Excluir"
>
<Trash2 className="h-4 w-4 text-red-600" /> <Trash2 className="h-4 w-4 text-red-600" />
</Button> </Button>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0" title="Mais Ações"> <Button
<span className="sr-only">Mais Ações</span> variant="ghost"
className="h-8 w-8 p-0"
title="Mais Ações"
>
<MoreVertical className="h-4 w-4" /> <MoreVertical className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
@ -270,55 +343,109 @@ export default function DoctorsPage() {
)} )}
</div> </div>
{/* Paginação */}
{doctors.length > itemsPerPage && (
<div className="flex flex-wrap justify-center items-center gap-2 mt-4 p-4 bg-white rounded-lg border border-gray-200 shadow-md">
{Array.from({ length: Math.ceil(doctors.length / itemsPerPage) }, (_, i) => (
<button
key={i}
onClick={() => paginate(i + 1)}
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm ${
currentPage === i + 1
? "bg-green-600 text-white shadow-md"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
{i + 1}
</button>
))}
</div>
)}
{/* Dialogs */}
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}> <AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Confirma a exclusão?</AlertDialogTitle> <AlertDialogTitle>Confirma a exclusão?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
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.
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel disabled={loading}>Cancelar</AlertDialogCancel> <AlertDialogCancel disabled={loading}>Cancelar</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} className="bg-red-600 hover:bg-red-700" disabled={loading}> <AlertDialogAction
{loading ? ( onClick={handleDelete}
className="bg-red-600 hover:bg-red-700"
disabled={loading}
>
{loading && (
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Loader2 className="w-4 h-4 mr-2 animate-spin" />
) : null} )}
Excluir Excluir
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
<AlertDialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}> <AlertDialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}>
<AlertDialogContent> <AlertDialogContent className="max-w-[95%] sm:max-w-lg">
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle className="text-2xl">{doctorDetails?.nome}</AlertDialogTitle> <AlertDialogTitle className="text-2xl">
{doctorDetails?.nome}
</AlertDialogTitle>
<AlertDialogDescription className="text-left text-gray-700"> <AlertDialogDescription className="text-left text-gray-700">
{doctorDetails && ( {doctorDetails && (
<div className="space-y-3 text-left"> <div className="space-y-3 text-left">
<h3 className="font-semibold mt-2">Informações Principais</h3> <h3 className="font-semibold mt-2">
<div className="grid grid-cols-2 gap-y-1 gap-x-4 text-sm"> Informações Principais
<div><strong>CRM:</strong> {doctorDetails.crm}</div> </h3>
<div><strong>Especialidade:</strong> {doctorDetails.especialidade}</div> <div className="grid grid-cols-1 sm:grid-cols-2 gap-y-2 gap-x-4 text-sm">
<div><strong>Celular:</strong> {doctorDetails.contato.celular || 'N/A'}</div> <div>
<div><strong>Localização:</strong> {`${doctorDetails.endereco.cidade || 'N/A'}/${doctorDetails.endereco.estado || 'N/A'}`}</div> <strong>CRM:</strong> {doctorDetails.crm}
</div>
<div>
<strong>Especialidade:</strong>{" "}
{doctorDetails.especialidade}
</div>
<div>
<strong>Celular:</strong>{" "}
{doctorDetails.contato.celular || "N/A"}
</div>
<div>
<strong>Localização:</strong>{" "}
{`${doctorDetails.endereco.cidade || "N/A"}/${
doctorDetails.endereco.estado || "N/A"
}`}
</div>
</div> </div>
<h3 className="font-semibold mt-4">Atendimento e Convênio</h3> <h3 className="font-semibold mt-4">
<div className="grid grid-cols-2 gap-y-1 gap-x-4 text-sm"> Atendimento e Convênio
<div><strong>Convênio:</strong> {doctorDetails.convenio || 'N/A'}</div> </h3>
<div><strong>VIP:</strong> {doctorDetails.vip ? "Sim" : "Não"}</div> <div className="grid grid-cols-1 sm:grid-cols-2 gap-y-2 gap-x-4 text-sm">
<div><strong>Status:</strong> {doctorDetails.status || 'N/A'}</div> <div>
<div><strong>Último atendimento:</strong> {doctorDetails.ultimo_atendimento || 'N/A'}</div> <strong>Convênio:</strong>{" "}
<div><strong>Próximo atendimento:</strong> {doctorDetails.proximo_atendimento || 'N/A'}</div> {doctorDetails.convenio || "N/A"}
</div>
<div>
<strong>VIP:</strong>{" "}
{doctorDetails.vip ? "Sim" : "Não"}
</div>
<div>
<strong>Status:</strong>{" "}
{doctorDetails.status || "N/A"}
</div>
<div>
<strong>Último atendimento:</strong>{" "}
{doctorDetails.ultimo_atendimento || "N/A"}
</div>
<div>
<strong>Próximo atendimento:</strong>{" "}
{doctorDetails.proximo_atendimento || "N/A"}
</div>
</div> </div>
</div> </div>
)}
{doctorDetails === null && !loading && (
<div className="text-red-600">Detalhes não disponíveis.</div>
)} )}
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>

View File

@ -46,20 +46,33 @@ export default function UsersPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
const [userDetails, setUserDetails] = useState<UserInfoResponse | null>(null); const [userDetails, setUserDetails] = useState<UserInfoResponse | null>(
null
);
const [selectedRole, setSelectedRole] = useState<string>(""); const [selectedRole, setSelectedRole] = useState<string>("");
// --- 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 () => { const fetchUsers = useCallback(async () => {
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
// 1) pega roles
const rolesData: any[] = await usersService.list_roles(); const rolesData: any[] = await usersService.list_roles();
// Garante que rolesData é array
const rolesArray = Array.isArray(rolesData) ? rolesData : []; const rolesArray = Array.isArray(rolesData) ? rolesData : [];
// 2) pega todos os profiles de uma vez (para evitar muitos requests) const profilesData: any[] = await api.get(
const profilesData: any[] = await api.get(`/rest/v1/profiles?select=id,full_name,email,phone`); `/rest/v1/profiles?select=id,full_name,email,phone`
);
const profilesById = new Map<string, any>(); const profilesById = new Map<string, any>();
if (Array.isArray(profilesData)) { if (Array.isArray(profilesData)) {
for (const p of 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 mapped: FlatUser[] = rolesArray.map((roleItem) => {
const uid = roleItem.user_id; const uid = roleItem.user_id;
const profile = profilesById.get(uid); const profile = profilesById.get(uid);
@ -82,6 +94,7 @@ export default function UsersPage() {
}); });
setUsers(mapped); setUsers(mapped);
setCurrentPage(1); // Resetar a página após carregar
console.log("[fetchUsers] mapped count:", mapped.length); console.log("[fetchUsers] mapped count:", mapped.length);
} catch (err: any) { } catch (err: any) {
console.error("Erro ao buscar usuários:", err); console.error("Erro ao buscar usuários:", err);
@ -95,7 +108,7 @@ export default function UsersPage() {
useEffect(() => { useEffect(() => {
const init = async () => { const init = async () => {
try { try {
await login(); // garante token await login();
} catch (e) { } catch (e) {
console.warn("login falhou no init:", e); console.warn("login falhou no init:", e);
} }
@ -115,7 +128,6 @@ export default function UsersPage() {
setUserDetails(data); setUserDetails(data);
} catch (err: any) { } catch (err: any) {
console.error("Erro ao carregar detalhes:", err); console.error("Erro ao carregar detalhes:", err);
// fallback com dados já conhecidos
setUserDetails({ setUserDetails({
user: { id: flatUser.user_id, email: flatUser.email }, user: { id: flatUser.user_id, email: flatUser.email },
profile: { full_name: flatUser.full_name, phone: flatUser.phone }, profile: { full_name: flatUser.full_name, phone: flatUser.phone },
@ -125,26 +137,48 @@ export default function UsersPage() {
} }
}; };
// 1. Filtragem
const filteredUsers = 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 ( return (
<ManagerLayout> <ManagerLayout>
<div className="space-y-6"> <div className="space-y-6 px-2 sm:px-4 md:px-8">
<div className="flex items-center justify-between"> {/* Header */}
<div className="flex flex-wrap items-center justify-between gap-3">
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">Usuários</h1> <h1 className="text-2xl font-bold text-gray-900">Usuários</h1>
<p className="text-sm text-gray-500">Gerencie usuários.</p> <p className="text-sm text-gray-500">Gerencie usuários.</p>
</div> </div>
<Link href="/manager/usuario/novo"> <Link href="/manager/usuario/novo" className="w-full sm:w-auto">
<Button className="bg-green-600 hover:bg-green-700"> <Button className="w-full sm:w-auto bg-green-600 hover:bg-green-700">
<Plus className="w-4 h-4 mr-2" /> Novo Usuário <Plus className="w-4 h-4 mr-2" /> Novo Usuário
</Button> </Button>
</Link> </Link>
</div> </div>
<div className="flex items-center space-x-4 bg-white p-4 rounded-lg border border-gray-200"> {/* Filtro e Itens por Página ADICIONADO */}
<div className="flex flex-wrap items-center gap-3 bg-white p-4 rounded-lg border border-gray-200">
<Filter className="w-5 h-5 text-gray-400" /> <Filter className="w-5 h-5 text-gray-400" />
{/* Select de Filtro por Papel */}
<Select onValueChange={setSelectedRole} value={selectedRole}> <Select onValueChange={setSelectedRole} value={selectedRole}>
<SelectTrigger className="w-[180px]"> <SelectTrigger className="w-[180px]">
<SelectValue placeholder="Filtrar por papel" /> <SelectValue placeholder="Filtrar por papel" />
@ -158,9 +192,25 @@ export default function UsersPage() {
<SelectItem value="user">Usuário</SelectItem> <SelectItem value="user">Usuário</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
{/* Select de Itens por Página ADICIONADO */}
<Select
onValueChange={handleItemsPerPageChange}
defaultValue={String(itemsPerPage)}
>
<SelectTrigger className="w-[140px]">
<SelectValue placeholder="Itens por pág." />
</SelectTrigger>
<SelectContent>
<SelectItem value="5">5 por página</SelectItem>
<SelectItem value="10">10 por página</SelectItem>
<SelectItem value="20">20 por página</SelectItem>
</SelectContent>
</Select>
</div> </div>
{/* Fim do Filtro e Itens por Página ADICIONADO */}
<div className="bg-white rounded-lg border border-gray-200 shadow-md overflow-hidden"> {/* Tabela */}
<div className="bg-white rounded-lg border border-gray-200 shadow-md overflow-x-auto">
{loading ? ( {loading ? (
<div className="p-8 text-center text-gray-500"> <div className="p-8 text-center text-gray-500">
<Loader2 className="w-8 h-8 animate-spin mx-auto mb-3 text-green-600" /> <Loader2 className="w-8 h-8 animate-spin mx-auto mb-3 text-green-600" />
@ -170,31 +220,62 @@ export default function UsersPage() {
<div className="p-8 text-center text-red-600">{error}</div> <div className="p-8 text-center text-red-600">{error}</div>
) : filteredUsers.length === 0 ? ( ) : filteredUsers.length === 0 ? (
<div className="p-8 text-center text-gray-500"> <div className="p-8 text-center text-gray-500">
Nenhum usuário encontrado. Nenhum usuário encontrado com os filtros aplicados.
</div> </div>
) : ( ) : (
<div className="overflow-x-auto"> <>
<table className="min-w-full divide-y divide-gray-200"> <table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50"> <thead className="bg-gray-50 hidden md:table-header-group">
<tr> <tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">ID</th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nome</th> ID
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">E-mail</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Telefone</th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Cargo</th> Nome
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">Ações</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
E-mail
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
Telefone
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
Cargo
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">
Ações
</th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
{filteredUsers.map((u) => ( {/* Usando currentItems para a paginação */}
<tr key={u.id} className="hover:bg-gray-50"> {currentItems.map((u) => (
<td className="px-6 py-4 text-sm text-gray-500">{u.id}</td> <tr
<td className="px-6 py-4 text-sm text-gray-900">{u.full_name}</td> key={u.id}
<td className="px-6 py-4 text-sm text-gray-500">{u.email}</td> className="flex flex-col md:table-row md:flex-row border-b md:border-0 hover:bg-gray-50"
<td className="px-6 py-4 text-sm text-gray-500">{u.phone}</td> >
<td className="px-6 py-4 text-sm text-gray-500 capitalize">{u.role}</td> <td className="px-6 py-4 text-sm text-gray-500 break-all md:whitespace-nowrap">
{u.id}
</td>
<td className="px-6 py-4 text-sm text-gray-900">
{u.full_name}
</td>
<td className="px-6 py-4 text-sm text-gray-500 break-all">
{u.email}
</td>
<td className="px-6 py-4 text-sm text-gray-500">
{u.phone}
</td>
<td className="px-6 py-4 text-sm text-gray-500 capitalize">
{u.role}
</td>
<td className="px-6 py-4 text-right"> <td className="px-6 py-4 text-right">
<Button variant="outline" size="icon" onClick={() => openDetailsDialog(u)} title="Visualizar"> <Button
variant="outline"
size="icon"
onClick={() => openDetailsDialog(u)}
title="Visualizar"
>
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
</Button> </Button>
</td> </td>
@ -202,14 +283,37 @@ export default function UsersPage() {
))} ))}
</tbody> </tbody>
</table> </table>
{/* Paginação ADICIONADA */}
{pageNumbers.length > 1 && (
<div className="flex flex-wrap justify-center items-center gap-2 mt-4 p-4 border-t border-gray-200">
{pageNumbers.map((number) => (
<button
key={number}
onClick={() => paginate(number)}
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm ${
currentPage === number
? "bg-green-600 text-white shadow-md"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
{number}
</button>
))}
</div> </div>
)} )}
{/* Fim da Paginação ADICIONADA */}
</>
)}
</div> </div>
{/* Modal de Detalhes */}
<AlertDialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}> <AlertDialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle className="text-2xl">{userDetails?.profile?.full_name || "Detalhes do Usuário"}</AlertDialogTitle> <AlertDialogTitle className="text-2xl">
{userDetails?.profile?.full_name || "Detalhes do Usuário"}
</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
{!userDetails ? ( {!userDetails ? (
<div className="p-4 text-center text-gray-500"> <div className="p-4 text-center text-gray-500">
@ -218,15 +322,33 @@ export default function UsersPage() {
</div> </div>
) : ( ) : (
<div className="space-y-3 pt-2 text-left text-gray-700"> <div className="space-y-3 pt-2 text-left text-gray-700">
<div><strong>ID:</strong> {userDetails.user.id}</div> <div>
<div><strong>E-mail:</strong> {userDetails.user.email}</div> <strong>ID:</strong> {userDetails.user.id}
<div><strong>Nome completo:</strong> {userDetails.profile.full_name}</div> </div>
<div><strong>Telefone:</strong> {userDetails.profile.phone}</div> <div>
<div><strong>Roles:</strong> {userDetails.roles?.join(", ")}</div> <strong>E-mail:</strong> {userDetails.user.email}
</div>
<div>
<strong>Nome completo:</strong>{" "}
{userDetails.profile.full_name}
</div>
<div>
<strong>Telefone:</strong> {userDetails.profile.phone}
</div>
<div>
<strong>Roles:</strong>{" "}
{userDetails.roles?.join(", ")}
</div>
<div> <div>
<strong>Permissões:</strong> <strong>Permissões:</strong>
<ul className="list-disc list-inside"> <ul className="list-disc list-inside">
{Object.entries(userDetails.permissions || {}).map(([k,v]) => <li key={k}>{k}: {v ? "Sim" : "Não"}</li>)} {Object.entries(
userDetails.permissions || {}
).map(([k, v]) => (
<li key={k}>
{k}: {v ? "Sim" : "Não"}
</li>
))}
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -139,12 +139,13 @@ export default function PacientesPage() {
</div> </div>
</div> </div>
<div className="flex flex-col md:flex-row flex-wrap gap-4 bg-card p-4 rounded-lg border border-border"> {/* Bloco de Filtros: Corrigido o uso de classes para responsividade e removida a duplicação dos filtros VIP e Aniversariantes */}
<div className="flex flex-wrap items-center gap-4 bg-card p-4 rounded-lg border border-border">
{/* Convênio */} {/* Convênio */}
<div className="flex items-center gap-2 w-full md:w-auto"> <div className="flex items-center gap-2 w-full sm:w-auto flex-grow">
<span className="text-sm font-medium text-foreground">Convênio</span> <span className="text-sm font-medium text-foreground whitespace-nowrap">Convênio</span>
<Select value={convenioFilter} onValueChange={setConvenioFilter}> <Select value={convenioFilter} onValueChange={setConvenioFilter}>
<SelectTrigger className="w-full md:w-40"> <SelectTrigger className="w-full sm:w-40">
<SelectValue placeholder="Selecione o Convênio" /> <SelectValue placeholder="Selecione o Convênio" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -156,37 +157,11 @@ export default function PacientesPage() {
</Select> </Select>
</div> </div>
<div className="flex items-center gap-2 w-full md:w-auto"> {/* VIP */}
<span className="text-sm font-medium text-foreground">VIP</span> <div className="flex items-center gap-2 w-full sm:w-auto flex-grow">
<span className="text-sm font-medium text-foreground whitespace-nowrap">VIP</span>
<Select value={vipFilter} onValueChange={setVipFilter}> <Select value={vipFilter} onValueChange={setVipFilter}>
<SelectTrigger className="w-full md:w-32"> <SelectTrigger className="w-full sm:w-32">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Todos</SelectItem>
<SelectItem value="vip">VIP</SelectItem>
<SelectItem value="regular">Regular</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2 w-full md:w-auto">
<span className="text-sm font-medium text-foreground">Aniversariantes</span>
<Select>
<SelectTrigger className="w-full md:w-32">
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
<SelectItem value="today">Hoje</SelectItem>
<SelectItem value="week">Esta semana</SelectItem>
<SelectItem value="month">Este mês</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-foreground">VIP</span>
<Select value={vipFilter} onValueChange={setVipFilter}>
<SelectTrigger className="w-32">
<SelectValue placeholder="Selecione" /> <SelectValue placeholder="Selecione" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -197,10 +172,11 @@ export default function PacientesPage() {
</Select> </Select>
</div> </div>
<div className="flex items-center gap-2"> {/* Aniversariantes */}
<span className="text-sm font-medium text-foreground">Aniversariantes</span> <div className="flex items-center gap-2 w-full sm:w-auto flex-grow">
<span className="text-sm font-medium text-foreground whitespace-nowrap">Aniversariantes</span>
<Select> <Select>
<SelectTrigger className="w-32"> <SelectTrigger className="w-full sm:w-32">
<SelectValue placeholder="Selecione" /> <SelectValue placeholder="Selecione" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -217,12 +193,13 @@ export default function PacientesPage() {
</Button> </Button>
</div> </div>
{/* Tabela com rolagem horizontal (overflow-x-auto) para responsividade */}
<div className="bg-white rounded-lg border border-gray-200"> <div className="bg-white rounded-lg border border-gray-200">
<div className="overflow-x-auto"> <div className="overflow-x-auto">
{error ? ( {error ? (
<div className="p-6 text-red-600">{`Erro ao carregar pacientes: ${error}`}</div> <div className="p-6 text-red-600">{`Erro ao carregar pacientes: ${error}`}</div>
) : ( ) : (
<table className="w-full min-w-[600px]"> <table className="w-full min-w-[700px]"> {/* Aumentei um pouco o min-width para garantir espaço */}
<thead className="bg-gray-50 border-b border-gray-200"> <thead className="bg-gray-50 border-b border-gray-200">
<tr> <tr>
<th className="text-left p-2 md:p-4 font-medium text-gray-700">Nome</th> <th className="text-left p-2 md:p-4 font-medium text-gray-700">Nome</th>