import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Link, useNavigate } from 'react-router-dom';
import {
PencilIcon, EyeIcon, ExclamationCircleIcon, IdentificationIcon,
UserIcon, EllipsisHorizontalIcon,
} from '@heroicons/react/24/outline';
import { api } from '../lib/api';
import type { ApiResponse } from '../lib/api';
import type { PatientRecord } from '../types';
import { formatNumber, toDate } from '../lib/utils';
import Pagination from '../components/ui/Pagination';
import PatientTagsCell from '../components/PatientTagsCell';
import PatientsFilterModal, { type PatientFilters } from '../components/PatientsFilterModal';
import {
SearchHeaderP, TurnsFilter, PatientsGridView, PatientsCategoryView, AddTurn,
} from '../components/icons/FilesToolbarIcons';
// tauri /files paginates card view by 16 and table view by 12.
const CARD_PAGE_SIZE = 16;
const TABLE_PAGE_SIZE = 12;
const EMPTY: PatientRecord[] = [];
/** unix start-of-day for `from`, end-of-day for `to`, from a gregorian Y-m-d. */
function dayBound(value: string | undefined, end: boolean): number | undefined {
if (!value) return undefined;
const d = toDate(value);
if (!d) return undefined;
const secs = Math.floor(d.setHours(0, 0, 0, 0) / 1000);
return end ? secs + 86399 : secs;
}
/** Number of filters currently applied (for the button badge). */
function countFilters(f: PatientFilters): number {
return [f.gender, f.insurance_id, f.admitted_from, f.admitted_to, f.service_status, f.has_debt || undefined, f.tags?.length ? '1' : undefined]
.filter(Boolean).length;
}
/**
* A single patient card — mirrors tauri `files/list/CardView` pixel-for-pixel
* (avatar + name + ⋮ menu header, file-number/mobile rows, tags footer).
*/
function PatientCard({ r, onView, onEdit }: { r: PatientRecord; onView: () => void; onEdit: () => void }) {
const [menu, setMenu] = useState(false);
return (
{ if ((e.target as HTMLElement).closest('.more-square-icon')) return; onView(); }}
>
{r.user_name || '—'}
{menu && (
<>
{ e.stopPropagation(); setMenu(false); }} />
>
)}
شماره پرونده:
{r.record_number || '—'}
موبایل:
{r.user_mobile || '—'}
e.stopPropagation()}
>
برچسبها:
);
}
/** پروندهها — patient records list. Ported from tauri /files (default card view). */
export default function PatientsListPage() {
const navigate = useNavigate();
const [page, setPage] = useState(1);
const [search, setSearch] = useState('');
const [view, setView] = useState<'table' | 'card'>('card');
const [filterOpen, setFilterOpen] = useState(false);
const [filters, setFilters] = useState
({});
const pageSize = view === 'card' ? CARD_PAGE_SIZE : TABLE_PAGE_SIZE;
const switchView = (v: 'table' | 'card') => { setView(v); setPage(1); };
const qs = new URLSearchParams({ page: String(page), limit: String(pageSize) });
if (search) qs.set('search', search);
if (filters.gender) qs.set('gender', filters.gender);
if (filters.insurance_id) qs.set('insurance_id', filters.insurance_id);
if (filters.service_status) qs.set('service_status', filters.service_status);
if (filters.has_debt) qs.set('has_debt', '1');
if (filters.tags?.length) qs.set('tags', filters.tags.join(','));
const af = dayBound(filters.admitted_from, false);
const at = dayBound(filters.admitted_to, true);
if (af) qs.set('admitted_from', String(af));
if (at) qs.set('admitted_to', String(at));
const { data, isLoading, error } = useQuery & { meta?: { totalRecords: number } }>({
queryKey: ['patients', qs.toString()],
queryFn: () => api.get(`/api/v1/patients?${qs.toString()}`),
});
const activeFilters = countFilters(filters);
const records = data?.data ?? EMPTY;
const total = data?.meta?.totalRecords ?? 0;
const editHref = (r: PatientRecord) => `/admin/patients/${r.uuid}/edit`;
const viewHref = (r: PatientRecord) => `/admin/patients/${r.uuid}`;
// نمایش جدولی/کارتی — دو دکمه با آیکون SVG سفارشی (عین tauri ViewModeToggle).
const viewToggle = (
);
return (
{/* Head — تیتر تنها (tauri files/head) */}
پروندهها
{/* Inputs — ردیف کنترلها (tauri files/inputs) */}
{/* جستجو + سوییچ نما */}
{/* فیلتر + تشکیل پرونده */}
{isLoading ? (
در حال بارگذاری...
) : error ? (
/* «دسترسی ندارید» با «بیماری یافت نشد» یکی نیست — پیام سرور را نشان بده. */
{(error as any)?.message || 'دسترسی به پروندهها امکانپذیر نیست'}
اگر بهتازگی محیط کاریتان تغییر کرده، از منوی بالا محیط درست را انتخاب کنید.
) : records.length === 0 ? (
) : view === 'table' ? (
{['ردیف', 'مراجعه کننده', 'برچسب ها', 'شماره پرونده', 'شماره تماس', 'کد ملی', 'عملیات'].map((h) => (
| {h} |
))}
{records.map((r, i) => (
| {formatNumber((page - 1) * pageSize + i + 1)} |
{r.user_name || '—'}
{!r.user_national_code && }
|
|
{r.record_number || '—'} |
{r.user_mobile || '—'} |
{r.user_national_code || '—'} |
|
))}
) : (
{records.map((r) => (
navigate(viewHref(r))} onEdit={() => navigate(editHref(r))} />
))}
)}
setFilterOpen(false)}
onApply={(f) => { setFilters(f); setPage(1); setFilterOpen(false); }}
/>
);
}