Files
clinicpro/.claude/prompt/implement-phase2-frontend.md
T

25 KiB
Raw Blame History

پیاده‌سازی فرانت‌اند فاز ۲ — 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:

{
  "success": true,
  "data": [
    {
      "uuid": "...",
      "full_name": "علی رضایی",
      "phone": "09121234567",
      "job_title": "پرستار",
      "address": null,
      "national_code": "0012345678",
      "active": true,
      "created_at": 1718000000
    }
  ]
}

Response PATCH toggle:

{ "success": true, "data": { "active": false } }

فایل‌های جدید

assets/admin/pages/StaffPage.tsx — صفحه مدیریت پرسنل:

  • useQuery(['staff'])GET /api/v1/staffdata?.data ?? []
  • جدول با ستون‌ها: نام، سمت، تلفن، کد ملی، وضعیت (badge)، عملیات
  • دکمه «افزودن پرسنل» → Modal ایجاد
  • دکمه ویرایش (PencilIcon) → Modal ویرایش
  • دکمه toggle (EyeIcon/EyeSlashIcon) با ConfirmDialog برای غیرفعال‌سازی
  • حذف سخت نداریم — فقط toggle

فیلدهای فرم:

interface StaffFormData {
  full_name: string;      // required
  phone?: string;
  job_title?: string;
  address?: string;
  national_code?: string;
}

Zod schema:

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 اضافه کن:

<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:

{
  "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:

{
  "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):

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

<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:
// ساختار 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: 'حذف' },
    ],
  },
};
  1. PATCH /api/v1/secretary/{uuid} با body:
{ "permissions": { "appointments": { "view": true, ... }, ... } }
  1. خطای ERR_SECRETARY_LIMIT_REACHED (403) را هنگام ایجاد منشی handle کن:
// در 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:

{
  "success": true,
  "data": [
    { "uuid": "...", "name": "تزریقات", "active": true }
  ]
}

Response GET /service-items/{sectionUuid}:

{
  "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('این سرویس در پرونده بیمار استفاده شده و قابل حذف نیست')

فرم آیتم:

interface ServiceItemForm {
  section_uuid: string;  // hidden — از state
  name: string;          // required
  price_rials: number;   // required
  staff_uuid?: string;   // SearchableSelect از GET /api/v1/staff
}

Types جدید:

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

<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:

{
  "success": true,
  "data": {
    "balance_rials": 150000,
    "sms_price_rials": 250,
    "estimated_sms_count": 600
  }
}

Response GET /sms/wallet/logs:

{
  "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:

{
  "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 جدید:

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

<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:

{
  "success": true,
  "data": [
    {
      "uuid": "...",
      "user": { "uuid": "...", "fullName": "علی محمدی", "phone": "09121234567" },
      "created_at": 1710000000
    }
  ],
  "meta": { "totalRecords": 12, "totalPages": 2, "currentPage": 1 }
}

Response GET /patient/{uuid}/sessions:

{
  "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:

{
  "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 ثبت مراجعه جدید:

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 در فرانت:
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 جدید:

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

// مسیر موجود کافی است:
<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:

{
  "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:

{
  "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

۱. فیلتر زمانی — اضافه کن:

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:

<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_balanceformatRial()
  • کارت «بیماران یکتا»: stats.unique_patients_count
  • کارت «درآمد دوره»: stats.revenue_period_rialsformatRial()

۳. نمودارها (برای admin role):

از SVG inline موجود در DashboardPage استفاده کن (کلاس SvgLineChart که در فایل موجود است):

// 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 — جدول ساده:

<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:

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