# راهنمای قدم‌به‌قدم صفحات پنل ادمین — فاز ۱ (زیرساخت + صفحهٔ نوبت‌ها)

## پروژه

`clinicpro` — پنل ادمین React داخل `assets/admin/`.

## زمینه

پنل ادمین حدود ۵۰ صفحه دارد و هیچ راهنمای درون‌برنامه‌ای ندارد.
کاربر تازه‌وارد نمی‌داند هر بخش صفحه چه کار می‌کند.
تصمیم گرفته شد راهنما به شکل **تور قدم‌به‌قدم** باشد.
یعنی المان‌ها یکی‌یکی highlight می‌شوند و کنارشان یک popover فارسی توضیح می‌دهد.

این فایل فقط **فاز ۱** است.
فاز ۱ = زیرساخت تور + پیاده‌سازی روی یک صفحهٔ نمونه.
صفحهٔ نمونه: `AppointmentsPage`.
دلیل انتخابش: شلوغ‌ترین صفحهٔ پنل است و المان‌های نقش‌محور دارد.
بعد از تأیید ظاهر و رفتار تور، فاز ۲ نوشته می‌شود که همین الگو را روی بقیهٔ صفحات تکرار می‌کند.

**در این فاز هیچ صفحهٔ دیگری را دست نزن.**

## مشکل / هدف

هدف:

- یک زیرساخت واحد برای تعریف تور هر صفحه.
- تعریف تور به‌صورت داده باشد، نه کد پراکنده در صفحات.
- المان‌های هدف با اتریبیوت `data-tour` مشخص شوند.
- استپی که المانش در DOM نیست بی‌سروصدا حذف شود، نه اینکه تور بشکند.
- بار اول ورود کاربر به صفحه، تور خودکار اجرا شود.
- بعد از دیدن، دیگر خودکار اجرا نشود؛ ولی با یک دکمهٔ `؟` قابل اجرای دوباره باشد.
- محتوای تور نسخه‌دار باشد؛ با بالا بردن نسخه، تور دوباره یک‌بار خودکار اجرا شود.

## تصمیم فنی — چرا driver.js

سه گزینه بررسی شد:

- `react-joyride` — سنگین‌تر و روی React 19 مشکوک است. `react-floater` هنوز peer آن React 18 است.
- کامپوننت دست‌ساز — منطق overlay و اسکرول و resize و sticky header باید از صفر نوشته شود. کد زیاد و باگ‌خیز.
- `driver.js` نسخهٔ ۱ — بدون dependency، حدود ۵ کیلوبایت gzip، مستقل از فریم‌ورک، خودش overlay و اسکرول و reposition را دارد.

انتخاب: **`driver.js`**.
استایلش با توکن‌های `styles.css` override می‌شود تا با تم روشن و تیره یکی شود.
راست‌چین بودن مشکلی ندارد چون ریشهٔ اپ `dir="rtl"` است.

نصب:

```bash
ddev exec npm install driver.js@^1.3.6
```

## معیار پذیرش

- ✅ موفق: ورود با `09390039833` به `/admin/appointments` برای اولین بار → تور خودکار اجرا می‌شود. متن‌ها فارسی‌اند. شمارنده «۱ از N» است. دکمه‌ها «بعدی / قبلی / باشه، فهمیدم». بعد از پایان، در `localStorage['clinicpro-tours']` کلید `seen.appointments` برابر نسخهٔ تور می‌شود. رفرش صفحه → تور دیگر خودکار اجرا نمی‌شود. کلیک روی دکمهٔ `؟` کنار عنوان → تور دوباره از استپ اول اجرا می‌شود.
- ❌ خطا: `useTour('does-not-exist')` → هیچ دکمه‌ای رندر نمی‌شود، هیچ خطایی throw نمی‌شود و تور اجرا نمی‌شود. همچنین اگر هیچ‌کدام از المان‌های تور در DOM نباشد، `start()` هیچ کاری نمی‌کند و crash نمی‌دهد.
- ⚠️ مرزی: ورود با نقش `doctor` که تب پزشکان ندارد، و منشیِ بدون مجوز `appointments.create` که دکمهٔ «افزودن نوبت» ندارد → استپ‌های مربوط به آن المان‌ها حذف می‌شوند، تور با استپ‌های کمتر اجرا می‌شود و شمارنده درست است، مثلاً «۱ از ۵» نه «۱ از ۷». همچنین بالا بردن `version` تور → یک‌بار دیگر خودکار اجرا می‌شود.

## فایل‌های مرتبط

| فایل | نقش |
|------|-----|
| `assets/admin/lib/tour/types.ts` | جدید — تعریف تایپ استپ و تور |
| `assets/admin/lib/tour/resolveSteps.ts` | جدید — تابع خالص فیلتر استپ‌ها بر اساس وجود المان |
| `assets/admin/lib/tour/registry.ts` | جدید — رجیستری تورها بر اساس id |
| `assets/admin/lib/tour/tours/appointments.ts` | جدید — تعریف تور صفحهٔ نوبت‌ها |
| `assets/admin/stores/tourStore.ts` | جدید — zustand persist برای تورهای دیده‌شده |
| `assets/admin/hooks/useTour.ts` | جدید — اجرای تور و اجرای خودکار بار اول |
| `assets/admin/components/ui/TourButton.tsx` | جدید — دکمهٔ `؟` راهنمای صفحه |
| `assets/admin/components/ui/PageHeader.tsx` | تغییر — پراپ اختیاری `tourId` |
| `assets/admin/pages/AppointmentsPage.tsx` | تغییر — افزودن `data-tour` و دکمهٔ راهنما |
| `assets/admin/styles.css` | تغییر — override استایل popover با توکن‌ها |
| `package.json` | تغییر — افزودن `driver.js` |

## وضعیت فعلی

`PageHeader` هیچ جای راهنما ندارد. کد فعلی:

```tsx
interface Props {
  title: string;
  breadcrumbs?: Crumb[];
  action?: React.ReactNode;
  description?: string;
  backTo?: string;
}

export default function PageHeader({ title, breadcrumbs, action, description, backTo }: Props) {
  ...
        <h1 className="section-title">{title}</h1>
```

`AppointmentsPage` از `PageHeader` استفاده نمی‌کند و عنوان دست‌ساز دارد. کد فعلی از خط ۴۲۰:

```tsx
  return (
    <div style={{ padding: '20px 24px' }}>
      <div style={{ maxWidth: 1050, margin: '0 auto' }}>
        {/* عنوان */}
        <h1 style={{ fontSize: 20, fontWeight: 700, color: 'var(--text)', marginBottom: 16 }}>نوبت‌ ها</h1>

        {/* نوار آمار */}
        <TurnsStatInfo stats={stats} />

        {/* نوار ابزار (بیرونِ کارت، مطابق طرح) */}
        <div style={{
          display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 16,
        }}>
          {/* سمت راست: تاریخ + سرویس + سوییچ نما (مطابق طرح) */}
          <DateNavigator date={selectedDate} onChange={setSelectedDate} />

          <ServiceFilterSelect ... />

          <TurnsViewToggle viewMode={viewMode} onChange={setViewMode} />

          <div style={{ flex: 1 }} />

          {/* سمت چپ: فیلتر + افزودن نوبت */}
          <button aria-label="فیلترها" className="btn sm" onClick={() => setFiltersOpen(true)} ... >
            <AdjustmentsHorizontalIcon style={{ width: 16 }} />
          </button>

          {!isRepresentation && canCreateAppt && (
            <button className="btn primary sm" onClick={...}>
              <PlusIcon style={{ width: 15, height: 15 }} />
              افزودن نوبت
            </button>
          )}
        </div>
```

الگوی persist موجود در `stores/uiStore.ts` مرجع است:

```ts
export const useUiStore = create<UiState>()(
  persist(
    (set, get) => ({ ... }),
    { name: 'clinicpro-ui', onRehydrateStorage: ... },
  ),
);
```

## وظایف

### ۱. نصب driver.js

```bash
ddev exec npm install driver.js@^1.3.6
```

**نحوه تست:** `driver.js` در `dependencies` فایل `package.json` باشد و `ddev exec yarn dev` بدون خطای resolve تمام شود.

---

### ۲. تایپ‌ها و تابع خالص resolve

`assets/admin/lib/tour/types.ts`:

```ts
export interface TourStep {
  /** مقدار اتریبیوت data-tour روی المان هدف */
  anchor: string;
  title: string;
  body: string;
  side?: 'top' | 'bottom' | 'left' | 'right';
}

export interface TourDefinition {
  /** شناسهٔ یکتا؛ معمولاً هم‌نام مسیر صفحه */
  id: string;
  /** با هر تغییر محتوای تور یکی زیاد شود تا تور یک‌بار دیگر خودکار اجرا شود */
  version: number;
  steps: TourStep[];
}
```

`assets/admin/lib/tour/resolveSteps.ts`:

```ts
import type { TourStep } from './types';

export function anchorSelector(anchor: string): string {
  return `[data-tour="${anchor}"]`;
}

/**
 * فقط استپ‌هایی می‌مانند که المانشان همین حالا در DOM هست.
 * دلیلش نقش‌محور بودن صفحات است: دکمهٔ «افزودن نوبت» برای منشیِ بدون مجوز
 * اصلاً رندر نمی‌شود و تور نباید روی یک المان غایب گیر کند.
 */
export function resolveSteps(steps: TourStep[], root: ParentNode = document): TourStep[] {
  return steps.filter((s) => root.querySelector(anchorSelector(s.anchor)) !== null);
}
```

**نحوه تست:** تست واحد `lib/tour/resolveSteps.test.ts` با vitest و jsdom:
استپ موجود می‌ماند، استپ غایب حذف می‌شود، ترتیب استپ‌های باقی‌مانده حفظ می‌شود، آرایهٔ خالی → خروجی خالی.

---

### ۳. رجیستری تورها

`assets/admin/lib/tour/registry.ts`:

```ts
import type { TourDefinition } from './types';
import { appointmentsTour } from './tours/appointments';

/** هر صفحه یک فایل جدا در tours/ دارد؛ اینجا فقط ثبت می‌شود. */
export const TOURS: Record<string, TourDefinition> = {
  [appointmentsTour.id]: appointmentsTour,
};

export function getTour(id?: string): TourDefinition | null {
  return id ? TOURS[id] ?? null : null;
}
```

هر تور در فایل خودش، تا فاز ۲ فقط «فایل جدید + یک خط ثبت» باشد.

`assets/admin/lib/tour/tours/appointments.ts`:

```ts
import type { TourDefinition } from '../types';

export const appointmentsTour: TourDefinition = {
  id: 'appointments',
  version: 1,
  steps: [
    { anchor: 'appointments-stats',   title: 'آمار امروز',        body: 'تعداد کل نوبت‌ها، انجام‌شده‌ها، در انتظار و لغوشده‌های همین روز.', side: 'bottom' },
    { anchor: 'appointments-date',    title: 'انتخاب روز',        body: 'با فلش‌ها یک روز جلو و عقب بروید یا از تقویم یک تاریخ را انتخاب کنید.', side: 'bottom' },
    { anchor: 'appointments-service', title: 'فیلتر خدمت',        body: 'فقط نوبت‌های یک خدمت مشخص را ببینید.', side: 'bottom' },
    { anchor: 'appointments-view',    title: 'نمای تایم‌لاین یا جدول', body: 'تایم‌لاین ساعت‌های روز را نشان می‌دهد و جدول فهرست ساده است.', side: 'bottom' },
    { anchor: 'appointments-filters', title: 'فیلترهای بیشتر',    body: 'فیلتر بر اساس وضعیت نوبت، بیمه و بیمار.', side: 'bottom' },
    { anchor: 'appointments-new',     title: 'ثبت نوبت جدید',     body: 'برای همان روز و همان پزشکِ انتخاب‌شده نوبت ثبت می‌کند.', side: 'bottom' },
    { anchor: 'appointments-doctors', title: 'تب پزشکان',         body: 'در کلینیک چندپزشکه، برنامهٔ هر پزشک را جدا ببینید.', side: 'bottom' },
    { anchor: 'appointments-list',    title: 'فهرست نوبت‌ها',     body: 'با کلیک روی هر نوبت وارد جزئیات و عملیات آن می‌شوید.', side: 'top' },
  ],
};
```

**نحوه تست:** تست واحد بررسی کند `id` تور خالی نیست، `version` عدد مثبت است و `anchor`ها تکراری نیستند.

---

### ۴. استور تورهای دیده‌شده

`assets/admin/stores/tourStore.ts` — دقیقاً الگوی `uiStore`:

```ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface TourState {
  /** tourId → نسخه‌ای که کاربر دیده است */
  seen: Record<string, number>;
  markSeen: (id: string, version: number) => void;
  isSeen: (id: string, version: number) => boolean;
  /** بدون آرگومان یعنی پاک کردن همهٔ تورها */
  reset: (id?: string) => void;
}

export const useTourStore = create<TourState>()(
  persist(
    (set, get) => ({
      seen: {},
      markSeen: (id, version) => set((s) => ({ seen: { ...s.seen, [id]: version } })),
      isSeen: (id, version) => (get().seen[id] ?? 0) >= version,
      reset: (id) => set((s) => {
        if (!id) return { seen: {} };
        const next = { ...s.seen };
        delete next[id];
        return { seen: next };
      }),
    }),
    { name: 'clinicpro-tours' },
  ),
);
```

**نحوه تست:** `stores/tourStore.test.ts` — ابتدا `isSeen('x', 1) === false`؛ بعد از `markSeen('x', 1)` برابر `true`؛ با `isSeen('x', 2)` دوباره `false`؛ `reset('x')` پاکش می‌کند.

---

### ۵. هوک useTour

`assets/admin/hooks/useTour.ts`:

```ts
import { useCallback, useEffect, useRef } from 'react';
import { driver } from 'driver.js';
import 'driver.js/dist/driver.css';
import { getTour } from '../lib/tour/registry';
import { anchorSelector, resolveSteps } from '../lib/tour/resolveSteps';
import { useTourStore } from '../stores/tourStore';

interface Options {
  /** وقتی true شد یعنی دادهٔ صفحه آمده و المان‌ها رندر شده‌اند */
  ready?: boolean;
}

export function useTour(tourId?: string, { ready = true }: Options = {}) {
  const tour = getTour(tourId);
  const markSeen = useTourStore((s) => s.markSeen);
  const isSeen = useTourStore((s) => s.isSeen);
  const autoStarted = useRef(false);

  const start = useCallback(() => {
    if (!tour) return;
    const steps = resolveSteps(tour.steps);
    if (steps.length === 0) return;

    const d = driver({
      showProgress: true,
      allowClose: true,
      overlayOpacity: 0.55,
      popoverClass: 'cp-tour',
      nextBtnText: 'بعدی',
      prevBtnText: 'قبلی',
      doneBtnText: 'باشه، فهمیدم',
      progressText: '{{current}} از {{total}}',
      steps: steps.map((s) => ({
        element: anchorSelector(s.anchor),
        popover: { title: s.title, description: s.body, side: s.side ?? 'bottom', align: 'start' },
      })),
      onDestroyed: () => markSeen(tour.id, tour.version),
    });
    d.drive();
  }, [tour, markSeen]);

  useEffect(() => {
    if (!tour || !ready || autoStarted.current) return;
    if (isSeen(tour.id, tour.version)) return;
    autoStarted.current = true;
    // یک فریم صبر تا چیدمان نهایی بنشیند و highlight سرِ جای درست بیفتد.
    const t = window.setTimeout(start, 300);
    return () => window.clearTimeout(t);
  }, [tour, ready, isSeen, start]);

  return { available: tour !== null, start };
}
```

نکته: `autoStarted` جلوی اجرای دوبارهٔ تور در رندرهای بعدی همان صفحه را می‌گیرد.

**نحوه تست:** تست کامپوننتی با mock کردن ماژول `driver.js`:
با تور دیده‌نشده و `ready: true`، بعد از پیش‌رفتن تایمر، `drive()` صدا زده می‌شود؛
با تور دیده‌شده صدا زده نمی‌شود؛ با `tourId` ناشناس هم صدا زده نمی‌شود.

---

### ۶. دکمهٔ راهنما

`assets/admin/components/ui/TourButton.tsx`:

```tsx
import { QuestionMarkCircleIcon } from '@heroicons/react/24/outline';
import { useTour } from '../../hooks/useTour';

/** دکمهٔ «؟» صفحه. اگر برای این صفحه توری ثبت نشده باشد، چیزی رندر نمی‌کند. */
export default function TourButton({ tourId }: { tourId?: string }) {
  const { available, start } = useTour(tourId, { ready: false });
  if (!available) return null;
  return (
    <button
      type="button"
      aria-label="راهنمای این صفحه"
      title="راهنمای این صفحه"
      onClick={start}
      style={{
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        width: 30, height: 30, borderRadius: 'var(--r-pill)',
        background: 'transparent', border: 'none', cursor: 'pointer', color: 'var(--text-3)',
      }}
    >
      <QuestionMarkCircleIcon style={{ width: 20, height: 20 }} />
    </button>
  );
}
```

مهم: در `TourButton` مقدار `ready: false` داده می‌شود تا **دکمه** مسئول اجرای خودکار نباشد.
اجرای خودکار وظیفهٔ خودِ صفحه است که می‌داند داده‌اش کی آماده است.

`PageHeader` یک پراپ اختیاری می‌گیرد و دکمه را کنار عنوان می‌گذارد:

```tsx
interface Props {
  title: string;
  breadcrumbs?: Crumb[];
  action?: React.ReactNode;
  description?: string;
  backTo?: string;
  /** شناسهٔ تور راهنمای این صفحه؛ اگر ثبت نشده باشد دکمه‌ای نمی‌آید */
  tourId?: string;
}

// ...
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
  <h1 className="section-title">{title}</h1>
  <TourButton tourId={tourId} />
</div>
```

`action` دست‌نخورده می‌ماند.

**نحوه تست:** `components/ui/TourButton.test.tsx` — با `tourId="appointments"` دکمه با `aria-label` «راهنمای این صفحه» رندر می‌شود؛ با `tourId="nope"` و بدون `tourId` هیچ دکمه‌ای رندر نمی‌شود.

---

### ۷. استایل popover با توکن‌های پروژه

در `assets/admin/styles.css` بعد از توکن‌ها:

```css
/* تور راهنما — ظاهر driver.js با توکن‌های پنل یکی می‌شود (روشن و تیره) */
.driver-popover.cp-tour {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
  font-family: inherit;
  max-width: 320px;
}
.driver-popover.cp-tour .driver-popover-title { color: var(--text); font-size: 14px; font-weight: 700; }
.driver-popover.cp-tour .driver-popover-description { color: var(--text-2); font-size: 13px; line-height: 1.9; }
.driver-popover.cp-tour .driver-popover-progress-text { color: var(--text-3); font-size: 12px; }
.driver-popover.cp-tour .driver-popover-navigation-btns button {
  background: var(--surface-2); color: var(--text-2);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  font-family: inherit; font-size: 12px; text-shadow: none;
}
.driver-popover.cp-tour .driver-popover-navigation-btns button:last-child {
  background: var(--primary); color: var(--on-primary); border-color: var(--primary);
}
.driver-popover.cp-tour .driver-popover-arrow-side-top    { border-top-color: var(--surface); }
.driver-popover.cp-tour .driver-popover-arrow-side-bottom { border-bottom-color: var(--surface); }
.driver-popover.cp-tour .driver-popover-arrow-side-left   { border-left-color: var(--surface); }
.driver-popover.cp-tour .driver-popover-arrow-side-right  { border-right-color: var(--surface); }
```

**نحوه تست:** چشمی. یک‌بار در تم روشن و یک‌بار در تم تیره تور را اجرا کن. متن و دکمه‌ها باید خوانا باشند و رنگ دکمهٔ آخر همان رنگ برند باشد.

---

### ۸. اتصال به صفحهٔ نوبت‌ها

در `pages/AppointmentsPage.tsx`:

اجرای خودکار وقتی دادهٔ صفحه آمد:

```tsx
const { start: startTour } = useTour('appointments', { ready: !isLoading });
```

`isLoading` را از همان `useQuery` نوبت‌های صفحه بگیر؛ اسم متغیر واقعی را از کد بردار، نگذار حدس زده شود.

عنوان صفحه دکمهٔ راهنما بگیرد:

```tsx
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginBottom: 16 }}>
  <h1 style={{ fontSize: 20, fontWeight: 700, color: 'var(--text)' }}>نوبت‌ ها</h1>
  <TourButton tourId="appointments" />
</div>
```

اتریبیوت‌ها روی همان hostهای موجود، بدون تغییر در کامپوننت‌های فرزند:

```tsx
<div data-tour="appointments-stats"><TurnsStatInfo stats={stats} /></div>

<div data-tour="appointments-date"><DateNavigator date={selectedDate} onChange={setSelectedDate} /></div>

<div data-tour="appointments-service"><ServiceFilterSelect ... /></div>

<div data-tour="appointments-view"><TurnsViewToggle viewMode={viewMode} onChange={setViewMode} /></div>

<button data-tour="appointments-filters" aria-label="فیلترها" ... />

<button data-tour="appointments-new" className="btn primary sm" ... />
```

روی کارت اصلی `data-tour="appointments-list"` و روی بلوک `showDoctorTabs` مقدار `data-tour="appointments-doctors"`.

قید مهم: `TurnsStatInfo`، `TurnsViewToggle`، `DoctorTabs` و `ServiceFilterSelect` **تغییر نکنند**.
فقط دور آن‌ها یک `div` با `data-tour` گذاشته شود.
دلیلش این است که این کامپوننت‌ها جای دیگری هم استفاده می‌شوند و تور نباید داخلشان نشت کند.
مواظب باش `div` اضافه چیدمان `flex` نوار ابزار را نشکند؛ اگر شکست، `display: 'contents'` روی wrapper بگذار
یا `data-tour` را مستقیم روی ریشهٔ همان کامپوننت از طریق پراپ عبور بده — گزینهٔ دوم فقط اگر گزینهٔ اول جواب نداد.

**نحوه تست:**

```bash
ddev exec npx tsc --noEmit --project tsconfig.json
ddev exec yarn test
ddev exec yarn dev
```

بعد ورود دستی با `09390039833 / 09390039833` و باز کردن `/admin/appointments`.
سناریوهای بخش «معیار پذیرش» یکی‌یکی چک شوند.
برای تست دوبارهٔ اجرای خودکار، در کنسول مرورگر:

```js
localStorage.removeItem('clinicpro-tours'); location.reload();
```

---

## نکات مهم

- محتوای تور فقط **داده** است، در `lib/tour/tours/*.ts`. هیچ متن راهنمایی داخل JSX صفحات نوشته نشود. هدف این است که فاز ۲ برای هر صفحه فقط «یک فایل تور + چند `data-tour` + یک `tourId`» باشد.
- `resolveSteps` عمداً یک تابع خالص جداست تا بدون رندر کردن صفحه تست شود.
- استپ غایب = حذف بی‌صدا. هیچ استپی نباید «اجباری» باشد، چون همهٔ صفحات پنل نقش‌محورند.
- `version` تور دلیل وجودی دارد: متن راهنما که عوض شد، کاربر قدیمی هم باید یک‌بار ببیندش. بدون version هیچ‌وقت دوباره نمایش داده نمی‌شود.
- کلید `localStorage` جدید `clinicpro-tours` است. با `clinicpro-auth` و `clinicpro-ui` قاطی نشود.
- اجرای خودکار حتماً به `ready` گره بخورد. اگر قبل از آمدن داده اجرا شود، المان‌ها هنوز نیستند و تور خالی می‌ماند.
- خروجیِ این فاز باید همان تصمیمِ نهایی «نوع helper» باشد. اگر ظاهر یا لحن متن‌ها مطلوب نبود، فقط `tours/appointments.ts` و بلوک CSS عوض می‌شوند، نه معماری.
- طبق قاعدهٔ پروژه هیچ تسکی بدون تست موفق و خطا و مرزی تمام نیست. تست‌های بند ۲ و ۴ و ۵ و ۶ اجباری‌اند.
- این تغییر backend ندارد، پس `docs/api/` دست نمی‌خورد.
- فاز ۲ بعد از تأیید نوشته می‌شود: تکرار همین الگو روی بقیهٔ صفحات، با تکیه بر پراپ `tourId` در `PageHeader` که ۸۲ نقطهٔ استفاده دارد.
