diff --git a/.claude/prompt/implement-phase2-frontend.md b/.claude/prompt/implement-phase2-frontend.md new file mode 100644 index 00000000..7fd1da6f --- /dev/null +++ b/.claude/prompt/implement-phase2-frontend.md @@ -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 +} /> +``` + +در 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; + 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 }; + period?: { label: string; duration_months: number }; + is_trial: boolean; + starts_at?: number; + expires_at?: number | null; + used_trial: boolean; + days_remaining?: number; +} +``` + +### Route + +```tsx +} /> +``` + +### کامیت +``` +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 +} /> +``` + +### کامیت +``` +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 +} /> +``` + +### کامیت +``` +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 +// مسیر موجود کافی است: +} /> +``` + +### کامیت +``` +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 +
+ {(['this_week', 'this_month', '3_months'] as Preset[]).map(p => ( + + ))} +
+``` + +**۲. فیلدهای جدید در کارت‌های آمار:** +- کارت «موجودی پیامک»: `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) ?? []; + + + +``` + +**subscription_sales_by_plan** — جدول ساده: +```tsx + + + + {subData.map(row => ( + + + + + + ))} + +
پلنتعداددرآمد
{row.plan}{row.count}{formatRial(row.revenue)}
+``` + +**API call با from/to:** +```tsx +const [preset, setPreset] = useState('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[]` | +| `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 |