Adição de Barra de pesquisa
This commit is contained in:
parent
248e90595e
commit
634542dff7
@ -2,8 +2,13 @@
|
|||||||
|
|
||||||
import { useEffect, useState, useCallback } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
import {
|
||||||
import { Eye, Edit, Calendar, Trash2, Loader2, MoreVertical } from "lucide-react";
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Eye, Edit, Calendar, Trash2, Loader2, MoreVertical, Filter } from "lucide-react";
|
||||||
import { api } from "@/services/api.mjs";
|
import { api } from "@/services/api.mjs";
|
||||||
import { PatientDetailsModal } from "@/components/ui/patient-details-modal";
|
import { PatientDetailsModal } from "@/components/ui/patient-details-modal";
|
||||||
import {
|
import {
|
||||||
@ -13,6 +18,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Sidebar from "@/components/Sidebar";
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
@ -35,6 +41,9 @@ interface Paciente {
|
|||||||
complement?: string;
|
complement?: string;
|
||||||
neighborhood?: string;
|
neighborhood?: string;
|
||||||
cep?: string;
|
cep?: string;
|
||||||
|
// NOVOS CAMPOS PARA O FILTRO
|
||||||
|
convenio?: string;
|
||||||
|
vip?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PacientesPage() {
|
export default function PacientesPage() {
|
||||||
@ -44,19 +53,44 @@ export default function PacientesPage() {
|
|||||||
const [selectedPatient, setSelectedPatient] = useState<Paciente | null>(null);
|
const [selectedPatient, setSelectedPatient] = useState<Paciente | null>(null);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
// --- Lógica de Paginação INÍCIO ---
|
// --- ESTADOS DOS FILTROS ---
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [convenioFilter, setConvenioFilter] = useState("todos");
|
||||||
|
const [vipFilter, setVipFilter] = useState("todos");
|
||||||
|
|
||||||
|
// --- Lógica de Filtragem ---
|
||||||
|
const filteredPacientes = pacientes.filter((p) => {
|
||||||
|
// 1. Filtro de Texto (Nome ou Telefone)
|
||||||
|
const searchLower = searchTerm.toLowerCase();
|
||||||
|
const matchesSearch = p.nome?.toLowerCase().includes(searchLower) || p.telefone?.includes(searchLower);
|
||||||
|
|
||||||
|
// 2. Filtro de Convênio
|
||||||
|
// Se for "todos", passa. Se não, verifica se o convênio do paciente é igual ao selecionado.
|
||||||
|
const matchesConvenio = convenioFilter === "todos" || (p.convenio?.toLowerCase() === convenioFilter);
|
||||||
|
|
||||||
|
// 3. Filtro VIP
|
||||||
|
// Se for "todos", passa. Se não, verifica se o status VIP é igual ao selecionado.
|
||||||
|
const matchesVip = vipFilter === "todos" || (p.vip?.toLowerCase() === vipFilter);
|
||||||
|
|
||||||
|
return matchesSearch && matchesConvenio && matchesVip;
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Lógica de Paginação ---
|
||||||
const [itemsPerPage, setItemsPerPage] = useState(10);
|
const [itemsPerPage, setItemsPerPage] = useState(10);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
const totalPages = Math.ceil(pacientes.length / itemsPerPage);
|
// Resetar página quando qualquer filtro mudar
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [searchTerm, convenioFilter, vipFilter, itemsPerPage]);
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(filteredPacientes.length / itemsPerPage);
|
||||||
const indexOfLastItem = currentPage * itemsPerPage;
|
const indexOfLastItem = currentPage * itemsPerPage;
|
||||||
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
||||||
const currentItems = pacientes.slice(indexOfFirstItem, indexOfLastItem);
|
const currentItems = filteredPacientes.slice(indexOfFirstItem, indexOfLastItem);
|
||||||
|
|
||||||
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
|
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
|
||||||
|
|
||||||
// Funções de Navegação
|
|
||||||
const goToPrevPage = () => {
|
const goToPrevPage = () => {
|
||||||
setCurrentPage((prev) => Math.max(1, prev - 1));
|
setCurrentPage((prev) => Math.max(1, prev - 1));
|
||||||
};
|
};
|
||||||
@ -65,7 +99,6 @@ export default function PacientesPage() {
|
|||||||
setCurrentPage((prev) => Math.min(totalPages, prev + 1));
|
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 getVisiblePageNumbers = (totalPages: number, currentPage: number) => {
|
||||||
const pages: number[] = [];
|
const pages: number[] = [];
|
||||||
const maxVisiblePages = 5;
|
const maxVisiblePages = 5;
|
||||||
@ -90,12 +123,10 @@ export default function PacientesPage() {
|
|||||||
|
|
||||||
const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage);
|
const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage);
|
||||||
|
|
||||||
// Lógica para mudar itens por página, resetando para a página 1
|
|
||||||
const handleItemsPerPageChange = (value: string) => {
|
const handleItemsPerPageChange = (value: string) => {
|
||||||
setItemsPerPage(Number(value));
|
setItemsPerPage(Number(value));
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
};
|
};
|
||||||
// --- Lógica de Paginação FIM ---
|
|
||||||
|
|
||||||
const handleOpenModal = (patient: Paciente) => {
|
const handleOpenModal = (patient: Paciente) => {
|
||||||
setSelectedPatient(patient);
|
setSelectedPatient(patient);
|
||||||
@ -113,7 +144,7 @@ export default function PacientesPage() {
|
|||||||
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);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return dateString; // Retorna o string original se o formato for inválido
|
return dateString;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -135,7 +166,7 @@ export default function PacientesPage() {
|
|||||||
cidade: p.city ?? "N/A",
|
cidade: p.city ?? "N/A",
|
||||||
estado: p.state ?? "N/A",
|
estado: p.state ?? "N/A",
|
||||||
ultimoAtendimento: formatDate(p.created_at),
|
ultimoAtendimento: formatDate(p.created_at),
|
||||||
proximoAtendimento: "N/A", // Necessita de lógica de agendamento real
|
proximoAtendimento: "N/A",
|
||||||
email: p.email ?? "N/A",
|
email: p.email ?? "N/A",
|
||||||
birth_date: p.birth_date ?? "N/A",
|
birth_date: p.birth_date ?? "N/A",
|
||||||
cpf: p.cpf ?? "N/A",
|
cpf: p.cpf ?? "N/A",
|
||||||
@ -147,10 +178,14 @@ export default function PacientesPage() {
|
|||||||
complement: p.complement ?? "N/A",
|
complement: p.complement ?? "N/A",
|
||||||
neighborhood: p.neighborhood ?? "N/A",
|
neighborhood: p.neighborhood ?? "N/A",
|
||||||
cep: p.cep ?? "N/A",
|
cep: p.cep ?? "N/A",
|
||||||
|
|
||||||
|
// ⚠️ ATENÇÃO: Verifique o nome real desses campos na sua API
|
||||||
|
// Se a API não retorna, estou colocando valores padrão para teste
|
||||||
|
convenio: p.insurance_plan || p.convenio || "Unimed", // Exemplo: mapeie o campo correto
|
||||||
|
vip: p.is_vip ? "Sim" : "Não", // Exemplo: se for booleano converta para string
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setPacientes(mapped);
|
setPacientes(mapped);
|
||||||
setCurrentPage(1); // Resetar a página ao carregar novos dados
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error("Erro ao carregar pacientes:", e);
|
console.error("Erro ao carregar pacientes:", e);
|
||||||
setError(e?.message || "Erro ao carregar pacientes");
|
setError(e?.message || "Erro ao carregar pacientes");
|
||||||
@ -166,215 +201,206 @@ export default function PacientesPage() {
|
|||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
||||||
{/* Cabeçalho */}
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||||
{" "}
|
|
||||||
{/* Ajustado para flex-col em telas pequenas */}
|
|
||||||
<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 text-sm sm:text-base">
|
<p className="text-muted-foreground text-sm sm:text-base">
|
||||||
Lista de pacientes vinculados
|
Lista de pacientes vinculados
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* 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 */}
|
|
||||||
<div className="flex flex-wrap gap-3 mt-4 sm:mt-0 w-full sm:w-auto justify-start sm:justify-end">
|
|
||||||
<Select
|
|
||||||
onValueChange={handleItemsPerPageChange}
|
|
||||||
defaultValue={String(itemsPerPage)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full sm: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>
|
</div>
|
||||||
|
|
||||||
<div className="bg-card rounded-lg border border-border overflow-hidden shadow-md">
|
{/* --- BARRA DE PESQUISA COM FILTROS ATIVOS --- */}
|
||||||
{/* Tabela para Telas Médias e Grandes */}
|
<div className="flex flex-col md:flex-row gap-4 items-center p-2 border border-border rounded-lg bg-card shadow-sm">
|
||||||
<div className="overflow-x-auto hidden md:block"> {/* Esconde em telas pequenas */}
|
|
||||||
<table className="min-w-[600px] w-full">
|
{/* Input de Busca */}
|
||||||
<thead className="bg-muted border-b border-border">
|
<div className="flex items-center gap-3 flex-1 w-full px-2">
|
||||||
<tr>
|
<Filter className="w-5 h-5 text-muted-foreground flex-shrink-0" />
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground">Nome</th>
|
<Input
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground">
|
type="text"
|
||||||
Telefone
|
placeholder="Buscar por nome ou telefone..."
|
||||||
</th>
|
value={searchTerm}
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden lg:table-cell">
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
Cidade
|
className="border-0 focus-visible:ring-0 shadow-none bg-transparent px-0 h-auto text-base placeholder:text-muted-foreground"
|
||||||
</th>
|
/>
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden lg:table-cell">
|
</div>
|
||||||
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>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{loading ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={7} className="p-6 text-muted-foreground text-center">
|
|
||||||
<Loader2 className="w-6 h-6 animate-spin mx-auto text-primary" />
|
|
||||||
Carregando pacientes...
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : error ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={7} className="p-6 text-red-600 text-center">{`Erro: ${error}`}</td>
|
|
||||||
</tr>
|
|
||||||
) : pacientes.length === 0 ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={7} className="p-8 text-center text-muted-foreground">
|
|
||||||
Nenhum paciente encontrado
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
currentItems.map((p) => (
|
|
||||||
<tr
|
|
||||||
key={p.id}
|
|
||||||
className="border-b border-border hover:bg-accent/40 transition-colors"
|
|
||||||
>
|
|
||||||
<td className="p-3 sm:p-4">{p.nome}</td>
|
|
||||||
<td className="p-3 sm:p-4 text-muted-foreground">
|
|
||||||
{p.telefone}
|
|
||||||
</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>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
|
||||||
<span className="sr-only">Abrir menu</span>
|
|
||||||
<MoreVertical className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem onClick={() => handleOpenModal(p)}>
|
|
||||||
<Eye className="w-4 h-4 mr-2" />
|
|
||||||
Ver detalhes
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem asChild>
|
|
||||||
<Link href={`/doctor/medicos/${p.id}/laudos`}>
|
|
||||||
<Edit className="w-4 h-4 mr-2" />
|
|
||||||
Laudos
|
|
||||||
</Link>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
{/* <DropdownMenuItem onClick={() => alert(`Agenda para paciente ID: ${p.id}`)}>
|
|
||||||
<Calendar className="w-4 h-4 mr-2" />
|
|
||||||
Ver agenda
|
|
||||||
</DropdownMenuItem> */}
|
|
||||||
{/* <DropdownMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
|
||||||
Excluir
|
|
||||||
</DropdownMenuItem> */}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Layout em Cards/Lista para Telas Pequenas */}
|
{/* Filtros e Paginação */}
|
||||||
<div className="md:hidden divide-y divide-border"> {/* Visível apenas em telas pequenas */}
|
<div className="flex flex-wrap items-center gap-4 w-full md:w-auto px-2 border-t md:border-t-0 md:border-l border-border pt-2 md:pt-0 justify-end">
|
||||||
{loading ? (
|
|
||||||
<div className="p-6 text-muted-foreground text-center">
|
{/* FILTRO CONVÊNIO */}
|
||||||
<Loader2 className="w-6 h-6 animate-spin mx-auto text-primary" />
|
<div className="flex items-center gap-2">
|
||||||
Carregando pacientes...
|
<span className="text-sm font-medium whitespace-nowrap text-muted-foreground hidden lg:inline">Convênio</span>
|
||||||
</div>
|
<Select value={convenioFilter} onValueChange={setConvenioFilter}>
|
||||||
) : error ? (
|
<SelectTrigger className="w-[100px] h-8 border-border bg-transparent focus:ring-0">
|
||||||
<div className="p-6 text-red-600 text-center">{`Erro: ${error}`}</div>
|
<SelectValue placeholder="Todos" />
|
||||||
) : pacientes.length === 0 ? (
|
</SelectTrigger>
|
||||||
<div className="p-8 text-center text-muted-foreground">
|
<SelectContent>
|
||||||
Nenhum paciente encontrado
|
<SelectItem value="todos">Todos</SelectItem>
|
||||||
</div>
|
{/* Certifique-se que o 'value' aqui seja minúsculo para bater com a lógica do filtro */}
|
||||||
) : (
|
<SelectItem value="unimed">Unimed</SelectItem>
|
||||||
currentItems.map((p) => (
|
<SelectItem value="bradesco">Bradesco</SelectItem>
|
||||||
<div key={p.id} className="flex items-center justify-between p-4 hover:bg-accent/40 transition-colors">
|
<SelectItem value="particular">Particular</SelectItem>
|
||||||
<div className="flex-1 min-w-0 pr-4"> {/* Adicionado padding à direita */}
|
</SelectContent>
|
||||||
<div className="text-base font-semibold text-foreground break-words"> {/* Aumentado a fonte e break-words para evitar corte do nome */}
|
</Select>
|
||||||
{p.nome || "—"}
|
</div>
|
||||||
</div>
|
|
||||||
{/* Removido o 'truncate' e adicionado 'break-words' no telefone */}
|
|
||||||
<div className="text-sm text-muted-foreground break-words">
|
|
||||||
Telefone: **{p.telefone || "N/A"}**
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="ml-4 flex-shrink-0">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="outline" size="icon">
|
|
||||||
<Eye className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem onClick={() => handleOpenModal(p)}>
|
|
||||||
<Eye className="w-4 h-4 mr-2" />
|
|
||||||
Ver detalhes
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem asChild>
|
|
||||||
<Link href={`/doctor/pacientes/${p.id}/laudos`}>
|
|
||||||
<Edit className="w-4 h-4 mr-2" />
|
|
||||||
Laudos
|
|
||||||
</Link>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => alert(`Agenda para paciente ID: ${p.id}`)}>
|
|
||||||
<Calendar className="w-4 h-4 mr-2" />
|
|
||||||
Ver agenda
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
|
||||||
Excluir
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{/* FILTRO VIP */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium whitespace-nowrap text-muted-foreground hidden lg:inline">VIP</span>
|
||||||
|
<Select value={vipFilter} onValueChange={setVipFilter}>
|
||||||
|
<SelectTrigger className="w-[90px] h-8 border-border bg-transparent focus:ring-0">
|
||||||
|
<SelectValue placeholder="Todos" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="todos">Todos</SelectItem>
|
||||||
|
<SelectItem value="sim">Sim</SelectItem>
|
||||||
|
<SelectItem value="não">Não</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* PAGINAÇÃO */}
|
||||||
|
<div className="flex items-center gap-2 pl-2 md:border-l border-border">
|
||||||
|
<Select
|
||||||
|
onValueChange={handleItemsPerPageChange}
|
||||||
|
defaultValue={String(itemsPerPage)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[130px] h-8 border-border bg-transparent focus:ring-0">
|
||||||
|
<SelectValue placeholder="Paginação" />
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tabela de Dados */}
|
||||||
|
<div className="bg-card rounded-lg border border-border overflow-hidden shadow-md">
|
||||||
|
<div className="overflow-x-auto hidden md:block">
|
||||||
|
<table className="min-w-[600px] w-full">
|
||||||
|
<thead className="bg-muted border-b border-border">
|
||||||
|
<tr>
|
||||||
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground">Nome</th>
|
||||||
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground">Telefone</th>
|
||||||
|
{/* Coluna Convênio visível para teste */}
|
||||||
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden lg:table-cell">Convênio</th>
|
||||||
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden lg:table-cell">VIP</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">Ações</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{loading ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={7} className="p-6 text-muted-foreground text-center">
|
||||||
|
<Loader2 className="w-6 h-6 animate-spin mx-auto text-primary" />
|
||||||
|
Carregando pacientes...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : error ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={7} className="p-6 text-red-600 text-center">{`Erro: ${error}`}</td>
|
||||||
|
</tr>
|
||||||
|
) : filteredPacientes.length === 0 ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={7} className="p-8 text-center text-muted-foreground">
|
||||||
|
Nenhum paciente encontrado com esses filtros.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
currentItems.map((p) => (
|
||||||
|
<tr key={p.id} className="border-b border-border hover:bg-accent/40 transition-colors">
|
||||||
|
<td className="p-3 sm:p-4">{p.nome}</td>
|
||||||
|
<td className="p-3 sm:p-4 text-muted-foreground">{p.telefone}</td>
|
||||||
|
<td className="p-3 sm:p-4 text-muted-foreground hidden lg:table-cell">{p.convenio}</td>
|
||||||
|
<td className="p-3 sm:p-4 text-muted-foreground hidden lg:table-cell">{p.vip}</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">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||||
|
<span className="sr-only">Abrir menu</span>
|
||||||
|
<MoreVertical className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => handleOpenModal(p)}>
|
||||||
|
<Eye className="w-4 h-4 mr-2" />
|
||||||
|
Ver detalhes
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<Link href={`/doctor/medicos/${p.id}/laudos`}>
|
||||||
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
|
Laudos
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cards para Mobile */}
|
||||||
|
<div className="md:hidden divide-y divide-border">
|
||||||
|
{loading ? (
|
||||||
|
<div className="p-6 text-muted-foreground text-center">
|
||||||
|
<Loader2 className="w-6 h-6 animate-spin mx-auto text-primary" />
|
||||||
|
Carregando...
|
||||||
|
</div>
|
||||||
|
) : filteredPacientes.length === 0 ? (
|
||||||
|
<div className="p-8 text-center text-muted-foreground">
|
||||||
|
Nenhum paciente encontrado.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
currentItems.map((p) => (
|
||||||
|
<div key={p.id} className="flex items-center justify-between p-4 hover:bg-accent/40 transition-colors">
|
||||||
|
<div className="flex-1 min-w-0 pr-4">
|
||||||
|
<div className="text-base font-semibold text-foreground break-words">
|
||||||
|
{p.nome || "—"}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{p.telefone} | {p.convenio} | VIP: {p.vip}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-4 flex-shrink-0">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" size="icon">
|
||||||
|
<Eye className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => handleOpenModal(p)}>
|
||||||
|
<Eye className="w-4 h-4 mr-2" />
|
||||||
|
Ver detalhes
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<Link href={`/doctor/pacientes/${p.id}/laudos`}>
|
||||||
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
|
Laudos
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Paginação */}
|
{/* Paginação */}
|
||||||
{totalPages > 1 && (
|
{totalPages > 1 && (
|
||||||
<div className="flex flex-wrap justify-center items-center gap-2 border-t border-border p-4 bg-muted/40">
|
<div className="flex flex-wrap justify-center items-center gap-2 border-t border-border p-4 bg-muted/40">
|
||||||
{/* Botão Anterior */}
|
|
||||||
<button
|
<button
|
||||||
onClick={goToPrevPage}
|
onClick={goToPrevPage}
|
||||||
disabled={currentPage === 1}
|
disabled={currentPage === 1}
|
||||||
@ -383,7 +409,6 @@ export default function PacientesPage() {
|
|||||||
{"< Anterior"}
|
{"< Anterior"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Números das Páginas */}
|
|
||||||
{visiblePageNumbers.map((number) => (
|
{visiblePageNumbers.map((number) => (
|
||||||
<button
|
<button
|
||||||
key={number}
|
key={number}
|
||||||
@ -398,7 +423,6 @@ export default function PacientesPage() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Botão Próximo */}
|
|
||||||
<button
|
<button
|
||||||
onClick={goToNextPage}
|
onClick={goToNextPage}
|
||||||
disabled={currentPage === totalPages}
|
disabled={currentPage === totalPages}
|
||||||
@ -411,11 +435,11 @@ export default function PacientesPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PatientDetailsModal
|
<PatientDetailsModal
|
||||||
patient={selectedPatient}
|
patient={selectedPatient}
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
onClose={handleCloseModal}
|
onClose={handleCloseModal}
|
||||||
/>
|
/>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user