Files
clinicpro/.claude/prompt/implement-phase2-frontend.md
T
hamed df7a784701 feat: implement realistic data seeding for doctors, clinics, and secretaries
- Added seed_realistic_data.php to clean existing data and populate the database with realistic entries for doctors, clinics, and secretaries.
- Created a structured approach to generate 100 doctors per city with diverse specialties and services.
- Implemented database cleanup routines to ensure a fresh start for data seeding.
- Enhanced the DoctorSecretaryRepository with improved comments for clarity.
2026-06-15 14:18:25 +03:30

947 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# پیاده‌سازی فرانت‌اند فاز ۲ — 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 |