- Implemented ClinicFormPage for adding new clinics with validation. - Created MyFinancialPage to display financial summaries and charts. - Developed MyPatientsPage for managing patient data with search and pagination. - Added PreRegistrationsPage for handling pre-registration requests with approval and rejection functionalities. - Introduced database migration for pre_registrations table. - Built PreRegistrationController for managing pre-registration logic, including submission, approval, and rejection. - Created PreRegistration entity and repository for handling pre-registration data.
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { PhoneIcon } from '@heroicons/react/24/outline';
|
|
import { api } from '../lib/api';
|
|
import type { PaginatedResponse } from '../lib/api';
|
|
import { formatDate, formatNumber } from '../lib/utils';
|
|
import DataTable, { type Column } from '../components/ui/DataTable';
|
|
import Pagination from '../components/ui/Pagination';
|
|
import PageHeader from '../components/ui/PageHeader';
|
|
|
|
interface Patient {
|
|
uuid: string;
|
|
name: string;
|
|
mobile: string;
|
|
total_appointments: number;
|
|
last_appointment: number | null;
|
|
}
|
|
|
|
const EMPTY: Patient[] = [];
|
|
|
|
export default function MyPatientsPage() {
|
|
const [page, setPage] = useState(1);
|
|
const [search, setSearch] = useState('');
|
|
const limit = 20;
|
|
|
|
const { data, isLoading } = useQuery<PaginatedResponse<Patient>>({
|
|
queryKey: ['my-patients', page, search],
|
|
queryFn: () => api.get(`/api/v1/my/patients?page=${page}&limit=${limit}&search=${encodeURIComponent(search)}`),
|
|
});
|
|
|
|
const patients = data?.data ?? EMPTY;
|
|
const total = data?.meta?.totalRecords ?? 0;
|
|
|
|
const columns: Column<Patient>[] = [
|
|
{
|
|
key: 'name',
|
|
header: 'نام بیمار',
|
|
render: (p) => (
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
<div className="avatar sm" style={{ background: 'linear-gradient(145deg, oklch(0.62 0.15 256), oklch(0.48 0.16 256))', flexShrink: 0 }}>
|
|
{(p.name || '؟').charAt(0)}
|
|
</div>
|
|
<span>{p.name || '—'}</span>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
key: 'mobile',
|
|
header: 'موبایل',
|
|
render: (p) => (
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6, direction: 'ltr' }}>
|
|
<PhoneIcon style={{ width: 14, height: 14, color: 'var(--text-3)' }} />
|
|
<span>{p.mobile}</span>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
key: 'total_appointments',
|
|
header: 'تعداد نوبت',
|
|
render: (p) => <span className="badge blue">{formatNumber(p.total_appointments)}</span>,
|
|
},
|
|
{
|
|
key: 'last_appointment',
|
|
header: 'آخرین نوبت',
|
|
render: (p) => p.last_appointment ? formatDate(String(p.last_appointment)) : '—',
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="page">
|
|
<PageHeader title="بیماران من" description={`${formatNumber(total)} بیمار`} />
|
|
|
|
<DataTable
|
|
columns={columns}
|
|
data={patients}
|
|
loading={isLoading}
|
|
searchValue={search}
|
|
onSearchChange={(v) => { setSearch(v); setPage(1); }}
|
|
searchPlaceholder="جستجوی نام یا موبایل..."
|
|
emptyMessage="بیماری یافت نشد"
|
|
/>
|
|
|
|
<Pagination page={page} total={total} limit={limit} onPageChange={setPage} />
|
|
</div>
|
|
);
|
|
}
|