diff --git a/assets/admin/pages/PatientsListPage.test.tsx b/assets/admin/pages/PatientsListPage.test.tsx index ca947786..d039cecd 100644 --- a/assets/admin/pages/PatientsListPage.test.tsx +++ b/assets/admin/pages/PatientsListPage.test.tsx @@ -31,21 +31,31 @@ beforeEach(() => { }); describe('PatientsListPage (پرونده‌ها)', () => { - it('renders the records table with the create button', async () => { + it('renders records as cards by default', async () => { renderWithProviders(, { route: '/admin/patients' }); expect(await screen.findByText('دنیا خلیلی')).toBeInTheDocument(); expect(screen.getByText('علی بدیعی زاده')).toBeInTheDocument(); + // card-only labels (with colon) — proves the default view is the card grid + expect(screen.getAllByText('شماره پرونده:').length).toBe(2); expect(screen.getByRole('button', { name: /تشکیل پرونده/ })).toBeInTheDocument(); - // record with no tags shows the inline "add" trigger - expect(screen.getByText('اضافه کردن')).toBeInTheDocument(); + // default card view: requests 16 per page + expect(get.mock.calls.some(([u]) => String(u).includes('limit=16'))).toBe(true); }); - it('switches to the card view', async () => { + it('opens the card ⋮ menu with view/edit actions', async () => { renderWithProviders(, { route: '/admin/patients' }); await screen.findByText('دنیا خلیلی'); - fireEvent.click(screen.getByLabelText('نمایش کارتی')); - expect(screen.getByText('دنیا خلیلی')).toBeInTheDocument(); - expect(screen.getAllByText('مشاهده').length).toBeGreaterThan(0); + fireEvent.click(screen.getAllByLabelText('عملیات')[0]); + expect(await screen.findByText('مشاهده')).toBeInTheDocument(); + expect(screen.getByText('ویرایش')).toBeInTheDocument(); + }); + + it('switches to the table view', async () => { + renderWithProviders(, { route: '/admin/patients' }); + await screen.findByText('دنیا خلیلی'); + fireEvent.click(screen.getByLabelText('نمایش جدولی')); + expect(await screen.findByText('مراجعه کننده')).toBeInTheDocument(); // table header + expect(get.mock.calls.some(([u]) => String(u).includes('limit=12'))).toBe(true); }); it('assigns a tag inline through the برچسب‌ها popover', async () => { diff --git a/assets/admin/pages/PatientsListPage.tsx b/assets/admin/pages/PatientsListPage.tsx index 2c6ed934..72fb4f0d 100644 --- a/assets/admin/pages/PatientsListPage.tsx +++ b/assets/admin/pages/PatientsListPage.tsx @@ -4,6 +4,7 @@ import { Link, useNavigate } from 'react-router-dom'; import { PlusIcon, PencilIcon, EyeIcon, MagnifyingGlassIcon, AdjustmentsHorizontalIcon, Squares2X2Icon, TableCellsIcon, ExclamationCircleIcon, IdentificationIcon, + UserIcon, EllipsisHorizontalIcon, } from '@heroicons/react/24/outline'; import { api } from '../lib/api'; import type { ApiResponse } from '../lib/api'; @@ -13,7 +14,9 @@ import Pagination from '../components/ui/Pagination'; import PatientTagsCell from '../components/PatientTagsCell'; import PatientsFilterModal, { type PatientFilters } from '../components/PatientsFilterModal'; -const LIMIT = 20; +// 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. */ @@ -31,16 +34,83 @@ function countFilters(f: PatientFilters): number { .filter(Boolean).length; } -/** پرونده‌ها — patient records list (table + card views) matching the Figma design. */ +/** + * A single patient card — mirrors tauri `files/list/CardView` pixel-for-pixel + * (avatar + name + ⋮ menu header, file-number/mobile rows, tags footer). + * The whole card navigates to the detail; the ⋮ menu offers view/edit. + */ +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'>('table'); + const [view, setView] = useState<'table' | 'card'>('card'); const [filterOpen, setFilterOpen] = useState(false); const [filters, setFilters] = useState({}); - const qs = new URLSearchParams({ page: String(page), limit: String(LIMIT) }); + 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); @@ -58,7 +128,6 @@ export default function PatientsListPage() { }); const activeFilters = countFilters(filters); - const records = data?.data ?? EMPTY; const total = data?.meta?.totalRecords ?? 0; @@ -81,10 +150,10 @@ export default function PatientsListPage() {
{/* view toggle */}
- -
@@ -105,7 +174,7 @@ export default function PatientsListPage() { ) : records.length === 0 ? (
-
پرونده‌ای یافت نشد.
+
بیماری یافت نشد
) : view === 'table' ? (
@@ -120,7 +189,7 @@ export default function PatientsListPage() { {records.map((r, i) => ( - {formatNumber((page - 1) * LIMIT + i + 1)} + {formatNumber((page - 1) * pageSize + i + 1)} {r.user_name || '—'} @@ -145,29 +214,15 @@ export default function PatientsListPage() {
) : ( -
+
{records.map((r) => ( -
-
- {r.user_name || '—'} - -
-
- شماره پرونده: {r.record_number || '—'} - تماس: {r.user_mobile || '—'} - کد ملی: {r.user_national_code || '—'} -
-
- مشاهده - ویرایش -
-
+ navigate(viewHref(r))} onEdit={() => navigate(editHref(r))} /> ))}
)}
- +