feat: add clinic management and financial reporting features
- 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.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user