191 lines
6.6 KiB
TypeScript
191 lines
6.6 KiB
TypeScript
import React from "react";
|
|
import { AvatarInitials } from "../AvatarInitials";
|
|
|
|
export interface PatientListItem {
|
|
id: string;
|
|
nome: string;
|
|
cpf?: string;
|
|
email?: string;
|
|
telefoneFormatado?: string; // já formatado externamente
|
|
convenio?: string | null;
|
|
vip?: boolean;
|
|
cidade?: string;
|
|
estado?: string;
|
|
// placeholders a serem preenchidos quando consultasService estiver pronto
|
|
ultimoAtendimento?: string | null; // ISO ou texto humanizado
|
|
proximoAtendimento?: string | null;
|
|
}
|
|
|
|
interface PatientListTableProps {
|
|
pacientes: PatientListItem[];
|
|
onEdit: (paciente: PatientListItem) => void;
|
|
onDelete: (paciente: PatientListItem) => void;
|
|
onView?: (paciente: PatientListItem) => void;
|
|
onSchedule?: (paciente: PatientListItem) => void;
|
|
emptyMessage?: string;
|
|
}
|
|
|
|
const PatientListTable: React.FC<PatientListTableProps> = ({
|
|
pacientes,
|
|
onEdit,
|
|
onDelete,
|
|
onView,
|
|
onSchedule,
|
|
emptyMessage = "Nenhum paciente encontrado.",
|
|
}) => {
|
|
return (
|
|
<div
|
|
className="overflow-x-auto"
|
|
role="region"
|
|
aria-label="Lista de pacientes"
|
|
>
|
|
<table
|
|
className="min-w-full divide-y divide-gray-200 dark:divide-gray-700"
|
|
role="table"
|
|
>
|
|
<thead className="bg-gray-50 dark:bg-gray-800" role="rowgroup">
|
|
<tr>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"
|
|
>
|
|
Paciente
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"
|
|
>
|
|
Contato
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"
|
|
>
|
|
Local
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"
|
|
>
|
|
Último Atendimento
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"
|
|
>
|
|
Próximo Atendimento
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"
|
|
>
|
|
Convênio
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider"
|
|
>
|
|
Ações
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody
|
|
className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"
|
|
role="rowgroup"
|
|
>
|
|
{pacientes.map((p) => (
|
|
<tr
|
|
key={p.id}
|
|
className="hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
role="row"
|
|
>
|
|
<td className="px-6 py-4">
|
|
<div className="flex items-start gap-3">
|
|
<AvatarInitials name={p.nome} size={40} />
|
|
<div>
|
|
<div
|
|
className="text-sm font-medium text-gray-900 dark:text-gray-100 cursor-pointer hover:underline"
|
|
onClick={() => onView?.(p)}
|
|
>
|
|
{p.nome || "Sem nome"}
|
|
</div>
|
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
|
{p.cpf || "CPF não informado"}
|
|
</div>
|
|
{p.vip && (
|
|
<div
|
|
className="mt-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold bg-yellow-100 text-yellow-800 dark:bg-yellow-200 dark:text-yellow-900"
|
|
aria-label="Paciente VIP"
|
|
>
|
|
VIP
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 text-sm">
|
|
<div className="text-gray-900 dark:text-gray-100">
|
|
{p.email || "Não informado"}
|
|
</div>
|
|
<div className="text-gray-500 dark:text-gray-400">
|
|
{p.telefoneFormatado || "Telefone não informado"}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
|
{p.cidade || p.estado
|
|
? `${p.cidade || ""}${p.cidade && p.estado ? "/" : ""}${
|
|
p.estado || ""
|
|
}`
|
|
: "—"}
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
|
{p.ultimoAtendimento || "—"}
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
|
{p.proximoAtendimento || "—"}
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
|
{p.convenio || "Particular"}
|
|
</td>
|
|
<td className="px-6 py-4 text-right text-sm font-medium space-x-3">
|
|
{onSchedule && (
|
|
<button
|
|
onClick={() => onSchedule(p)}
|
|
className="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300"
|
|
>
|
|
Agendar
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={() => onEdit(p)}
|
|
className="text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300"
|
|
>
|
|
Editar
|
|
</button>
|
|
<button
|
|
onClick={() => onDelete(p)}
|
|
className="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
|
|
>
|
|
Excluir
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
{pacientes.length === 0 && (
|
|
<tr>
|
|
<td
|
|
colSpan={7}
|
|
className="px-6 py-10 text-center text-sm text-gray-500 dark:text-gray-400"
|
|
>
|
|
{emptyMessage}
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PatientListTable;
|