feat: implement frontend for Phase 2 tasks including staff management, subscription, and clinic services

This commit is contained in:
hamed
2026-06-14 22:15:50 +03:30
parent b0244f28f5
commit 7ea3523830
+807
View File
@@ -0,0 +1,807 @@
# پیاده‌سازی فرانت‌اند فاز ۲ — TASK-10 تا TASK-16
## زمینه
بک‌اند فاز ۲ کاملاً پیاده‌سازی و تست شده است. این پرامپت فقط فرانت‌اند ۷ تسک را پیاده‌سازی می‌کند.
هر تسک که تمام شد → `git commit` بزن (message فارسی مختصر).
بعد از هر تسک بلافاصله `yarn dev` اجرا کن تا خطاهای TypeScript مشخص شوند.
## قوانین ثابت پروژه
- **Paginated**: items از `data?.data`، total از `data?.meta?.totalRecords`
- **Single resource**: از `data?.data` (ممکن است double-nested باشد — `data?.data?.data`)
- **Category API**: triple-nested → `data?.data?.data ?? []`
- **تاریخ نمایش**: همیشه با `formatDate()` یا `formatDateTime()` از `lib/utils`
- **Forms**: React Hook Form + Zod resolver
- **Data fetching**: TanStack Query v5 (`useQuery` / `useMutation`)
- **UI**: از `DataTable`, `Pagination`, `Modal`, `ConfirmDialog`, `PageHeader` استفاده کن — کتابخانه جدید اضافه نکن
- **RTL**: همه استایل‌ها RTL باشند
- **کامیت بعد از هر تسک**: `git add` فایل‌های تغییر‌یافته → `git commit -m "feat: ..."`
---
## TASK-10 — مدیریت پرسنل (Staff)
### API
```
GET /api/v1/staff → لیست پرسنل فعال entity جاری
POST /api/v1/staff → ایجاد
PATCH /api/v1/staff/{uuid} → ویرایش
PATCH /api/v1/staff/{uuid}/toggle → فعال/غیرفعال
```
**Response GET:**
```json
{
"success": true,
"data": [
{
"uuid": "...",
"full_name": "علی رضایی",
"phone": "09121234567",
"job_title": "پرستار",
"address": null,
"national_code": "0012345678",
"active": true,
"created_at": 1718000000
}
]
}
```
**Response PATCH toggle:**
```json
{ "success": true, "data": { "active": false } }
```
### فایل‌های جدید
**`assets/admin/pages/StaffPage.tsx`** — صفحه مدیریت پرسنل:
- `useQuery(['staff'])``GET /api/v1/staff``data?.data ?? []`
- جدول با ستون‌ها: نام، سمت، تلفن، کد ملی، وضعیت (badge)، عملیات
- دکمه «افزودن پرسنل» → Modal ایجاد
- دکمه ویرایش (PencilIcon) → Modal ویرایش
- دکمه toggle (EyeIcon/EyeSlashIcon) با `ConfirmDialog` برای غیرفعال‌سازی
- حذف سخت نداریم — فقط toggle
**فیلدهای فرم:**
```ts
interface StaffFormData {
full_name: string; // required
phone?: string;
job_title?: string;
address?: string;
national_code?: string;
}
```
**Zod schema:**
```ts
const schema = z.object({
full_name: z.string().min(2, 'نام حداقل ۲ کاراکتر باید باشد'),
phone: z.string().optional(),
job_title: z.string().optional(),
address: z.string().optional(),
national_code: z.string().optional(),
});
```
### Route و Sidebar
در `App.tsx` اضافه کن:
```tsx
<Route path="staff" element={<RoleRoute roles={['doctor', 'clinic']}><StaffPage /></RoleRoute>} />
```
در Sidebar (فایل `components/layout/AdminLayout.tsx` یا `Sidebar.tsx`) لینک «پرسنل» را برای نقش‌های `doctor` و `clinic` اضافه کن.
### کامیت
```
git add assets/admin/pages/StaffPage.tsx assets/admin/App.tsx assets/admin/components/layout/...
git commit -m "feat: add staff management page (TASK-10)"
```
---
## TASK-11 — پنل اشتراکی (Subscription)
### API
```
GET /api/v1/subscription/plans → لیست پلن‌ها (public)
GET /api/v1/subscription/my → اشتراک فعال من
POST /api/v1/subscription/trial → فعال‌سازی تریال
POST /api/v1/subscription-payment → شروع پرداخت { period_uuid, gateway }
```
**Response GET /plans:**
```json
{
"success": true,
"data": [
{
"uuid": "...",
"name": "basic",
"level": 1,
"max_secretaries": 2,
"features": { "patient_records": true, "services": true },
"periods": [
{ "uuid": "...", "label": "تریال ۱ ماهه", "duration_months": 1, "price_rials": 0, "is_trial": true },
{ "uuid": "...", "label": "۱ ماهه", "duration_months": 1, "price_rials": 250000, "is_trial": false }
]
}
]
}
```
**Response GET /my:**
```json
{
"success": true,
"data": {
"plan": { "name": "basic", "level": 1, "features": { "patient_records": true, "services": true } },
"period": { "label": "۶ ماهه", "duration_months": 6 },
"is_trial": false,
"starts_at": 1718000000,
"expires_at": 1733360000,
"used_trial": true,
"days_remaining": 42
}
}
```
### فایل‌های جدید
**`assets/admin/pages/SubscriptionPage.tsx`** — صفحه اشتراک:
**بخش ۱ — وضعیت فعلی:**
- نمایش پلن فعلی (نام، تاریخ انقضا به شمسی، روزهای باقی‌مانده)
- اگر `used_trial = false` → دکمه «فعال‌سازی تریال رایگان» (Basic یک ماهه)
- نوار پیشرفت روزهای باقیمانده (اگر `expires_at` دارد)
**بخش ۲ — کارت‌های پلن:**
- سه کارت: Free / Basic / Professional
- هر کارت: نام پلن، ویژگی‌ها (لیست چک‌مارک)، دوره‌های قیمت به صورت badge/tab
- دکمه «خرید» برای هر دوره → modal انتخاب gateway → `POST /api/v1/subscription-payment` → redirect به URL برگشتی
- اگر پلن فعلی همان پلن است → دکمه «تمدید»
**Types جدید (`assets/admin/types/index.ts`):**
```ts
export interface SubscriptionPlan {
uuid: string;
name: string;
level: number;
max_secretaries: number;
features: Record<string, boolean>;
periods: SubscriptionPeriod[];
}
export interface SubscriptionPeriod {
uuid: string;
label: string;
duration_months: number;
price_rials: number;
is_trial: boolean;
}
export interface MySubscription {
plan: { name: string; level: number; features: Record<string, boolean> };
period?: { label: string; duration_months: number };
is_trial: boolean;
starts_at?: number;
expires_at?: number | null;
used_trial: boolean;
days_remaining?: number;
}
```
### Route
```tsx
<Route path="subscription" element={<RoleRoute roles={['doctor', 'clinic']}><SubscriptionPage /></RoleRoute>} />
```
### کامیت
```
git commit -m "feat: add subscription management page (TASK-11)"
```
---
## TASK-12 — تکمیل Permissions منشی
**هیچ صفحه جدیدی لازم نیست** — فقط `assets/admin/pages/SecretariesPage.tsx` تغییر می‌کند.
### وضعیت فعلی SecretariesPage.tsx
فایل موجود است و لیست منشیان را نشان می‌دهد. باید:
1. **Modal ویرایش permissions** اضافه کن:
- دکمه «دسترسی‌ها» (ShieldCheckIcon) کنار هر ردیف
- Modal با checkbox matrix:
```tsx
// ساختار permissions که باید از API بخوانی و ذخیره کنی
const DEFAULT_PERMISSIONS = {
appointments: { view: true, create: false, cancel: false, update_status: false },
addresses: { view: true, create: false, update: false, delete: false },
clinic_info: { view: true, update: false },
insurances: { view: true, create: false, update: false, delete: false },
};
const PERMISSION_LABELS = {
appointments: {
label: 'نوبت‌ها',
actions: [
{ key: 'view', label: 'مشاهده' },
{ key: 'create', label: 'ایجاد' },
{ key: 'cancel', label: 'لغو' },
{ key: 'update_status', label: 'تغییر وضعیت' },
],
},
addresses: {
label: 'آدرس‌ها',
actions: [
{ key: 'view', label: 'مشاهده' },
{ key: 'create', label: 'ایجاد' },
{ key: 'update', label: 'ویرایش' },
{ key: 'delete', label: 'حذف' },
],
},
clinic_info: {
label: 'اطلاعات کلینیک',
actions: [
{ key: 'view', label: 'مشاهده' },
{ key: 'update', label: 'ویرایش' },
],
},
insurances: {
label: 'بیمه‌ها',
actions: [
{ key: 'view', label: 'مشاهده' },
{ key: 'create', label: 'ایجاد' },
{ key: 'update', label: 'ویرایش' },
{ key: 'delete', label: 'حذف' },
],
},
};
```
2. **PATCH `/api/v1/secretary/{uuid}`** با body:
```json
{ "permissions": { "appointments": { "view": true, ... }, ... } }
```
3. **خطای `ERR_SECRETARY_LIMIT_REACHED` (403)** را هنگام ایجاد منشی handle کن:
```tsx
// در onError useMutation create:
if (err.code === 'ERR_SECRETARY_LIMIT_REACHED') {
toast.error('سقف تعداد منشی پنل فعلی شما پر است. برای افزودن منشی بیشتر پنل را ارتقا دهید.');
}
```
### کامیت
```
git commit -m "feat: add secretary permissions matrix and subscription limit handling (TASK-12)"
```
---
## TASK-13 — سرویس‌های کلینیک
### API
```
GET /api/v1/service-sections → لیست بخش‌ها
POST /api/v1/service-section → ایجاد بخش { name }
PATCH /api/v1/service-section/{uuid} → ویرایش { name }
DELETE /api/v1/service-section/{uuid} → حذف بخش
GET /api/v1/service-items/{sectionUuid} → لیست آیتم‌های یک بخش
POST /api/v1/service-item → ایجاد آیتم
PATCH /api/v1/service-item/{uuid} → ویرایش آیتم
DELETE /api/v1/service-item/{uuid} → حذف آیتم
```
**Response GET /service-sections:**
```json
{
"success": true,
"data": [
{ "uuid": "...", "name": "تزریقات", "active": true }
]
}
```
**Response GET /service-items/{sectionUuid}:**
```json
{
"success": true,
"data": [
{
"uuid": "...",
"name": "سرم ۵۰۰cc",
"price_rials": 85000,
"staff": { "uuid": "...", "full_name": "علی رضایی" },
"active": true
}
]
}
```
### فایل‌های جدید
**`assets/admin/pages/ClinicServicesPage.tsx`** — صفحه دو‌بخشی:
**ستون چپ** — لیست بخش‌ها (ServiceSection):
- لیست ساده با دکمه‌های ویرایش/حذف
- دکمه «+ بخش جدید» → Modal با فیلد `name`
- کلیک روی بخش → بارگذاری آیتم‌های آن در ستون راست
**ستون راست** — لیست آیتم‌های بخش انتخابی (ServiceItem):
- جدول: نام، قیمت (`formatRial()`), پرسنل مسئول، وضعیت
- دکمه «+ آیتم جدید»
- حذف: اگر `ERR_SERVICE_ITEM_IN_USE` (409) → toast.error('این سرویس در پرونده بیمار استفاده شده و قابل حذف نیست')
**فرم آیتم:**
```ts
interface ServiceItemForm {
section_uuid: string; // hidden — از state
name: string; // required
price_rials: number; // required
staff_uuid?: string; // SearchableSelect از GET /api/v1/staff
}
```
**Types جدید:**
```ts
export interface ServiceSection {
uuid: string;
name: string;
active: boolean;
}
export interface ServiceItem {
uuid: string;
name: string;
price_rials: number;
staff: { uuid: string; full_name: string } | null;
active: boolean;
}
```
### Route
```tsx
<Route path="clinic-services" element={<RoleRoute roles={['doctor', 'clinic']}><ClinicServicesPage /></RoleRoute>} />
```
### کامیت
```
git commit -m "feat: add clinic services management page (TASK-13)"
```
---
## TASK-14 — پنل پیامکی
### API
```
GET /api/v1/sms/wallet/balance → موجودی
POST /api/v1/sms/wallet/charge → شارژ { gateway, amount_rials } → redirect URL
GET /api/v1/sms/wallet/logs → تاریخچه (paginated)
GET /api/v1/sms/settings → تنظیمات
PATCH /api/v1/sms/settings → ذخیره تنظیمات
```
**Response GET /sms/wallet/balance:**
```json
{
"success": true,
"data": {
"balance_rials": 150000,
"sms_price_rials": 250,
"estimated_sms_count": 600
}
}
```
**Response GET /sms/wallet/logs:**
```json
{
"success": true,
"data": [
{ "uuid": "...", "type": "credit", "amount_rials": 500000, "description": "شارژ", "created_at": 1718000000 },
{ "uuid": "...", "type": "debit", "amount_rials": 250, "description": "ارسال پیامک یادآوری", "created_at": 1718001000 }
],
"meta": { "totalRecords": 45, "totalPages": 5, "currentPage": 1 }
}
```
**Response GET /sms/settings:**
```json
{
"success": true,
"data": {
"reminder_enabled": true,
"reminder_hours_before": 3,
"post_visit_enabled": false,
"post_visit_text": null
}
}
```
### فایل‌های جدید
**`assets/admin/pages/SmsWalletPage.tsx`** — صفحه کیف پول پیامک:
**بخش ۱ — کارت موجودی:**
- نمایش `balance_rials` با `formatRial()`
- نمایش `estimated_sms_count` (تعداد پیامک قابل ارسال)
- دکمه «شارژ کیف پول» → Modal با فیلد مبلغ + انتخاب gateway → redirect به URL
**بخش ۲ — تنظیمات:**
- Toggle «یادآوری قبل از نوبت» + فیلد عددی «چند ساعت قبل»
- Toggle «پیامک بعد از ویزیت» + textarea متن پیامک
- دکمه ذخیره → `PATCH /api/v1/sms/settings`
**بخش ۳ — تاریخچه تراکنش‌ها:**
- جدول: نوع (badge سبز credit / قرمز debit)، مبلغ، توضیحات، تاریخ
- Pagination
**Types جدید:**
```ts
export interface SmsWalletBalance {
balance_rials: number;
sms_price_rials: number;
estimated_sms_count: number;
}
export interface SmsWalletLog {
uuid: string;
type: 'credit' | 'debit';
amount_rials: number;
description: string;
created_at: number;
}
export interface SmsSettings {
reminder_enabled: boolean;
reminder_hours_before: number;
post_visit_enabled: boolean;
post_visit_text: string | null;
}
```
### Route
```tsx
<Route path="sms-wallet" element={<RoleRoute roles={['doctor', 'clinic']}><SmsWalletPage /></RoleRoute>} />
```
### کامیت
```
git commit -m "feat: add SMS wallet and settings page (TASK-14)"
```
---
## TASK-15 — پرونده بیمار
### API
```
GET /api/v1/patients → لیست بیماران (paginated, ?search=)
POST /api/v1/patient → ایجاد پرونده { user_uuid }
GET /api/v1/patient/{uuid} → جزئیات پرونده
GET /api/v1/patient/{uuid}/sessions → لیست مراجعات (paginated)
POST /api/v1/patient/{uuid}/session → ثبت مراجعه جدید
PATCH /api/v1/session/{uuid} → ویرایش مراجعه
```
**Response GET /patients:**
```json
{
"success": true,
"data": [
{
"uuid": "...",
"user": { "uuid": "...", "fullName": "علی محمدی", "phone": "09121234567" },
"created_at": 1710000000
}
],
"meta": { "totalRecords": 12, "totalPages": 2, "currentPage": 1 }
}
```
**Response GET /patient/{uuid}/sessions:**
```json
{
"success": true,
"data": [
{
"uuid": "...",
"appointment_uuid": null,
"visit_price_rials": 500000,
"base_insurance_discount_percent": "30.00",
"supplementary_discount_percent": "0.00",
"services_total_rials": 130000,
"final_price_rials": 445000,
"payment_method": "cash",
"notes": "...",
"created_at": 1718000000,
"updated_at": 1718000000
}
],
"meta": { "totalRecords": 5, "totalPages": 1, "currentPage": 1 }
}
```
**Request POST /patient/{uuid}/session:**
```json
{
"visit_price_rials": 500000,
"base_insurance_discount_percent": 30,
"supplementary_discount_percent": 0,
"payment_method": "cash",
"notes": "...",
"services": [
{ "service_item_uuid": "...", "staff_uuid": null }
]
}
```
### فایل‌های جدید/تغییر
**`assets/admin/pages/MyPatientsPage.tsx`** — بازنویسی کامل:
**نمای لیست بیماران:**
- جستجو (`?search=`) با debounce ۴۰۰ms
- جدول: نام بیمار، تلفن، تاریخ ثبت، عملیات
- دکمه «+ بیمار جدید» → Modal با `SearchableSelect` برای پیدا کردن کاربر (search by phone)
- کلیک روی ردیف → نمای جزئیات پرونده (state محلی یا route `/my-patients/:uuid`)
**نمای جزئیات پرونده:**
- مشخصات بیمار + دکمه «+ مراجعه جدید»
- لیست مراجعات (accordion یا جدول):
- قیمت ویزیت، تخفیف بیمه پایه، تخفیف تکمیلی، مجموع سرویس‌ها، قیمت نهایی
- وضعیت پرداخت (`payment_method`)
- یادداشت
- دکمه ویرایش (فقط `notes` و `payment_method`)
**Modal ثبت مراجعه جدید:**
```ts
interface SessionFormData {
visit_price_rials: number;
base_insurance_discount_percent: number; // 0-100
supplementary_discount_percent: number; // 0-100
payment_method: 'cash' | 'card' | 'insurance' | 'online' | 'pending';
notes?: string;
services: { service_item_uuid: string; staff_uuid?: string }[];
}
```
- محاسبه `final_price_rials` به صورت live در فرانت:
```ts
const afterBase = visitPrice * (1 - baseDiscount / 100);
const afterSupp = afterBase * (1 - suppDiscount / 100);
const servicesTotal = services.reduce((sum, s) => sum + (s.price_rials || 0), 0);
const finalPrice = Math.round(afterSupp) + servicesTotal;
```
- افزودن سرویس: `SearchableSelect` از `GET /api/v1/service-items/{sectionUuid}` (باید ابتدا بخش را انتخاب کند)
**Types جدید:**
```ts
export interface PatientRecord {
uuid: string;
user: { uuid: string; fullName: string; phone: string };
created_at: number;
}
export interface PatientSession {
uuid: string;
appointment_uuid: string | null;
visit_price_rials: number;
base_insurance_discount_percent: string;
supplementary_discount_percent: string;
services_total_rials: number;
final_price_rials: number;
payment_method: string;
notes: string | null;
created_at: number;
updated_at: number;
}
```
### Route
```tsx
// مسیر موجود کافی است:
<Route path="my-patients" element={<RoleRoute roles={['doctor', 'secretary', 'clinic']}><MyPatientsPage /></RoleRoute>} />
```
### کامیت
```
git commit -m "feat: implement patient records and sessions page (TASK-15)"
```
---
## TASK-16 — داشبورد هوشمند با فیلتر زمانی
### تغییر API
```
GET /api/v1/dashboard/clinic?from=UNIX&to=UNIX → فیلدهای جدید در stats
GET /api/v1/dashboard/doctor?from=UNIX&to=UNIX → فیلدهای جدید در stats
GET /api/v1/admin/dashboard/charts?from=UNIX&to=UNIX → سری‌های زمانی
```
**فیلدهای جدید در Response clinic/doctor:**
```json
{
"stats": {
"...فیلدهای قبلی...",
"sms_wallet_balance": 150000,
"unique_patients_count": 45,
"revenue_period_rials": 12500000
},
"period": { "from": 1717200000, "to": 1719792000 }
}
```
**Response GET /admin/dashboard/charts?from=UNIX&to=UNIX:**
```json
{
"success": true,
"data": {
"appointments_by_day": [
{ "date": "06/01", "count": 12 }
],
"revenue_by_day": [
{ "date": "06/01", "amount": 3500000 }
],
"subscription_sales_by_plan": [
{ "plan": "basic", "count": 15, "revenue": 3750000 }
],
"appointment_status": [...],
"top_specialties": [...]
}
}
```
### تغییر `assets/admin/pages/DashboardPage.tsx`
**۱. فیلتر زمانی — اضافه کن:**
```tsx
type Preset = 'this_week' | 'this_month' | '3_months' | 'custom';
// تبدیل به Unix:
function getRange(preset: Preset): { from: number; to: number } {
const now = Math.floor(Date.now() / 1000);
switch (preset) {
case 'this_week':
// شنبه‌ی این هفته — ساده‌ترین راه: now - روزهای گذشته از شنبه
const dayOfWeek = new Date().getDay(); // 0=Sun,6=Sat
const daysSinceSat = (dayOfWeek + 1) % 7;
return { from: now - daysSinceSat * 86400, to: now };
case 'this_month':
const d = new Date(); d.setDate(1); d.setHours(0,0,0,0);
return { from: Math.floor(d.getTime() / 1000), to: now };
case '3_months':
return { from: now - 90 * 86400, to: now };
default:
return { from: now - 30 * 86400, to: now };
}
}
```
preset buttons:
```tsx
<div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
{(['this_week', 'this_month', '3_months'] as Preset[]).map(p => (
<button
key={p}
className={`btn sm ${preset === p ? 'primary' : ''}`}
onClick={() => setPreset(p)}
>
{p === 'this_week' ? 'این هفته' : p === 'this_month' ? 'این ماه' : '۳ ماه'}
</button>
))}
</div>
```
**۲. فیلدهای جدید در کارت‌های آمار:**
- کارت «موجودی پیامک»: `stats.sms_wallet_balance``formatRial()`
- کارت «بیماران یکتا»: `stats.unique_patients_count`
- کارت «درآمد دوره»: `stats.revenue_period_rials``formatRial()`
**۳. نمودارها (برای admin role):**
از **SVG inline** موجود در DashboardPage استفاده کن (کلاس `SvgLineChart` که در فایل موجود است):
```tsx
// appointments_by_day و revenue_by_day داده‌های خطی دارند
// فقط آرایه عددی را استخراج کن:
const apptData = charts?.appointments_by_day?.map((d: any) => d.count) ?? [];
const revData = charts?.revenue_by_day?.map((d: any) => d.amount) ?? [];
<SvgLineChart data={apptData} color="#3b82f6" />
<SvgLineChart data={revData} color="#22c55e" />
```
**subscription_sales_by_plan** — جدول ساده:
```tsx
<table>
<thead><tr><th>پلن</th><th>تعداد</th><th>درآمد</th></tr></thead>
<tbody>
{subData.map(row => (
<tr key={row.plan}>
<td>{row.plan}</td>
<td>{row.count}</td>
<td>{formatRial(row.revenue)}</td>
</tr>
))}
</tbody>
</table>
```
**API call با from/to:**
```tsx
const [preset, setPreset] = useState<Preset>('this_month');
const range = getRange(preset);
// برای admin:
const { data: charts } = useQuery({
queryKey: ['dashboard-charts', range.from, range.to],
queryFn: () => api.get(`/api/v1/admin/dashboard/charts?from=${range.from}&to=${range.to}`),
});
// برای doctor:
const { data: doctorDash } = useQuery({
queryKey: ['dashboard-doctor', range.from, range.to],
queryFn: () => api.get(`/api/v1/dashboard/doctor?from=${range.from}&to=${range.to}`),
enabled: primaryRole === 'doctor',
});
```
### کامیت
```
git commit -m "feat: add date range filter and new stats to dashboard (TASK-16)"
```
---
## ترتیب اجرا و چک‌لیست
```
[ ] TASK-10: StaffPage.tsx → route → sidebar → yarn dev → commit
[ ] TASK-11: SubscriptionPage.tsx → route → sidebar → yarn dev → commit
[ ] TASK-12: SecretariesPage.tsx (permissions modal + limit error) → yarn dev → commit
[ ] TASK-13: ClinicServicesPage.tsx → route → sidebar → yarn dev → commit
[ ] TASK-14: SmsWalletPage.tsx → route → sidebar → yarn dev → commit
[ ] TASK-15: MyPatientsPage.tsx (بازنویسی) → yarn dev → commit
[ ] TASK-16: DashboardPage.tsx (date range + new stats + charts) → yarn dev → commit
```
## قوانین اجرا
1. **یک تسک در یک زمان** — هرگز دو تسک را همزمان شروع نکن
2. **بعد از هر تسک `ddev exec yarn dev`** — هر TypeScript error را همان‌جا رفع کن
3. **اگر API error داشت** — فایل `docs/api/` مربوطه را بخوان
4. **Types جدید** را در `assets/admin/types/index.ts` اضافه کن
5. **Sidebar** — برای هر صفحه جدید، لینک sidebar را هم اضافه کن (در `AdminLayout.tsx` یا `Sidebar.tsx`)
6. **کامیت** بعد از `yarn dev` بدون خطا — نه قبل از آن
## فایل‌های مرجع مهم
| فایل | نقش |
|------|-----|
| `assets/admin/lib/api.ts` | fetch wrapper — `api.get/post/patch/delete` |
| `assets/admin/lib/utils.ts` | `formatRial`, `formatDate`, `formatDateTime`, `formatNumber` |
| `assets/admin/types/index.ts` | همه TypeScript interfaces |
| `assets/admin/stores/authStore.ts` | `primaryRole`, `dbUuid`, `doctorUuid` |
| `assets/admin/components/ui/DataTable.tsx` | جدول — `Column<T>[]` |
| `assets/admin/components/ui/Modal.tsx` | Modal |
| `assets/admin/components/ui/SearchableSelect.tsx` | dropdown با جستجو |
| `docs/api/staff.md` | API staff |
| `docs/api/subscription.md` | API subscription |
| `docs/api/clinic-services.md` | API services |
| `docs/api/sms.md` | API SMS wallet |
| `docs/api/patient.md` | API patient records |
| `docs/api/dashboard.md` | API dashboard |