Merge claude/youthful-mendel-0c569f: patient UI overhaul
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -399,12 +399,21 @@ const CopyField: React.FC<{ label: string; value?: string | null }> = ({ label,
|
||||
);
|
||||
};
|
||||
|
||||
type SortOpt = 'recentes' | 'az' | 'za' | 'convenio';
|
||||
const SORT_OPTS: { key: SortOpt; label: string }[] = [
|
||||
{ key: 'recentes', label: 'Recentes' },
|
||||
{ key: 'az', label: 'A→Z' },
|
||||
{ key: 'za', label: 'Z→A' },
|
||||
{ key: 'convenio', label: 'Convênio' },
|
||||
];
|
||||
|
||||
export const PatientsView: React.FC = () => {
|
||||
const [busca, setBusca] = useState('');
|
||||
const [sort, setSort] = useState<SortOpt>('recentes');
|
||||
const debouncedBusca = useDebounce(busca, 500);
|
||||
|
||||
const fetcher = useCallback(() => HybridBackend.getPacientes(debouncedBusca), [debouncedBusca]);
|
||||
const { data: result, isLoading, error, refresh } = useHybridBackend<{ data: Paciente[]; total: number }>(fetcher, [debouncedBusca]);
|
||||
const fetcher = useCallback(() => HybridBackend.getPacientes(debouncedBusca, sort), [debouncedBusca, sort]);
|
||||
const { data: result, isLoading, error, refresh } = useHybridBackend<{ data: Paciente[]; total: number }>(fetcher, [debouncedBusca, sort]);
|
||||
const pacientes = result?.data ?? [];
|
||||
const totalPacientes = result?.total ?? 0;
|
||||
|
||||
@@ -467,60 +476,58 @@ export const PatientsView: React.FC = () => {
|
||||
</button>
|
||||
</PageHeader>
|
||||
|
||||
<div className="bg-white p-4 rounded-xl border border-gray-200 shadow-sm mb-4">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
|
||||
{/* Search + sort bar */}
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none" size={16} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="DIGITE NOME OU CPF PARA BUSCAR..."
|
||||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none uppercase-input"
|
||||
placeholder="Nome ou CPF..."
|
||||
className="w-full pl-9 pr-3 py-2 border border-gray-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-100 focus:border-blue-300 outline-none bg-white"
|
||||
value={busca}
|
||||
onChange={(e) => setBusca(e.target.value.toUpperCase())}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 bg-gray-100 rounded-lg p-1">
|
||||
{SORT_OPTS.map(o => (
|
||||
<button
|
||||
key={o.key}
|
||||
onClick={() => setSort(o.key)}
|
||||
className={`px-2.5 py-1 rounded-md text-[11px] font-bold transition-all whitespace-nowrap ${sort === o.key ? 'bg-white text-gray-800 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}
|
||||
>
|
||||
{o.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<h3 className="text-sm font-bold text-gray-500 uppercase">
|
||||
{busca
|
||||
? `RESULTADOS PARA "${busca}" (${pacientes.length})`
|
||||
: `PACIENTES — exibindo ${pacientes.length} de ${totalPacientes} cadastrados`
|
||||
}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 overflow-y-auto pb-10">
|
||||
<div className="grid grid-cols-3 gap-3 overflow-y-auto pb-6">
|
||||
{isLoading && <div className="col-span-3 text-center p-10 text-gray-500 uppercase font-bold flex justify-center items-center gap-2"><Loader2 className="animate-spin" /> CARREGANDO DADOS DO POSTGRESQL...</div>}
|
||||
{error && <div className="col-span-3 text-center p-10 text-red-600 uppercase font-bold">ERRO AO CARREGAR: {error.message}</div>}
|
||||
|
||||
{displayedPacientes && displayedPacientes.map(p => (
|
||||
<div key={p.id} className="bg-white rounded-xl border border-gray-200 shadow-sm hover:shadow-lg transition-all duration-300 flex flex-col">
|
||||
<div className="p-4 flex-grow flex flex-col gap-3">
|
||||
{/* Nome completo + convênio */}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h3 className="font-bold text-blue-700 text-sm leading-snug uppercase break-words">{p.nome}</h3>
|
||||
<div key={p.id} className="bg-white rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-all duration-200 flex flex-col">
|
||||
<div className="px-3 pt-3 pb-2 flex-grow flex flex-col gap-2">
|
||||
<div className="flex items-start justify-between gap-1.5">
|
||||
<h3 className="font-bold text-blue-700 text-[11px] leading-snug uppercase break-words flex-1">{p.nome}</h3>
|
||||
{p.convenio && (
|
||||
<span className="bg-green-100 text-green-800 text-[10px] font-bold px-2 py-0.5 rounded-full uppercase shrink-0 mt-0.5">{p.convenio}</span>
|
||||
<span className="bg-green-100 text-green-700 text-[9px] font-black px-1.5 py-0.5 rounded-full uppercase shrink-0">{p.convenio}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Dados copiáveis */}
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
<div className="grid grid-cols-2 gap-1">
|
||||
<CopyField label="CPF" value={p.cpf} />
|
||||
<CopyField label="Telefone" value={p.telefone} />
|
||||
<CopyField label="Nascimento" value={p.dataNascimento} />
|
||||
<CopyField label="Convênio" value={p.convenio} />
|
||||
{p.email && <div className="col-span-2"><CopyField label="E-mail" value={p.email} /></div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex border-t border-gray-200 mt-auto">
|
||||
<button onClick={() => setModal({ type: 'clinical', patient: p })} className="w-1/2 flex items-center justify-center gap-2 py-2.5 text-blue-600 hover:bg-blue-50 rounded-bl-xl text-xs font-bold transition-colors uppercase">
|
||||
<FolderOpen size={14} /> Clínico
|
||||
<div className="flex border-t border-gray-100">
|
||||
<button onClick={() => setModal({ type: 'clinical', patient: p })} className="w-1/2 flex items-center justify-center gap-1.5 py-2 text-blue-600 hover:bg-blue-50 rounded-bl-xl text-[10px] font-bold transition-colors uppercase">
|
||||
<FolderOpen size={12} /> Clínico
|
||||
</button>
|
||||
<div className="border-l border-gray-200"></div>
|
||||
<button onClick={() => setModal({ type: 'financial', patient: p })} className="w-1/2 flex items-center justify-center gap-2 py-2.5 text-green-700 hover:bg-green-50 rounded-br-xl text-xs font-bold transition-colors uppercase">
|
||||
<DollarSign size={14} /> Financeiro
|
||||
<div className="border-l border-gray-100" />
|
||||
<button onClick={() => setModal({ type: 'financial', patient: p })} className="w-1/2 flex items-center justify-center gap-1.5 py-2 text-green-700 hover:bg-green-50 rounded-br-xl text-[10px] font-bold transition-colors uppercase">
|
||||
<DollarSign size={12} /> Financeiro
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user