diff --git a/CLAUDE.md b/CLAUDE.md
index c3476d47..54dddf9d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -139,13 +139,24 @@ Theming: **dark mode** overrides via `[data-theme="dark"]`; **compact density**
Reuse these before building new ones:
`DataTable` (sortable, search, skeleton loading, empty state, bulk) · `Modal` · `ConfirmDialog` ·
-`PageHeader` (title + breadcrumb + action) · `StatCard` · `StatusBadge` · `Pagination` ·
-`SearchableSelect` · `AppointmentStatusDropdown` · `PersianDateInput` / `PersianDatePicker` /
+`PageHeader` (title + breadcrumb + action + `backTo`) · `BackButton` · `StatCard` · `StatusBadge` ·
+`Pagination` · `SearchableSelect` · `AppointmentStatusDropdown` · `PersianDateInput` / `PersianDatePicker` /
`PersianCalendar` · `MobileInput` · `PriceInput` · `Portal` · `FeatureGate` · `Altcha` ·
`InviteDoctorModal` · `PwaInstallBanner` / `PwaLoginCard` / `NotificationMobileCard`.
Feature composites (not generic) live one level up in `components/*.tsx`.
+### دکمهٔ بازگشت — الزامی در صفحات زیرمجموعه
+
+هر صفحهای که از دل صفحهٔ دیگری باز میشود (جزئیات، فرم ساخت/ویرایش، زیرصفحههای تنظیمات)
+باید دکمهٔ «بازگشت» داشته باشد، با یک ظاهر و یک رفتار:
+
+- صفحاتی که `PageHeader` دارند: فقط `backTo="/admin/…"` بدهید.
+- بقیه: `` بالای هدر صفحه.
+- دکمهٔ دستساز نسازید — ظاهر مرجع `cp-btn-secondary` با ارتفاع ۳۶ و آیکون `ChevronRightIcon` است
+ (همان دکمهٔ صفحهٔ سرویسها) و رفتارش در `hooks/useGoBack.ts` متمرکز است: یک قدم عقب در تاریخچهٔ
+ پنل، و در ورود مستقیم/رفرش (`location.key === 'default'`) رفتن به `fallback`.
+
---
## Conventions
diff --git a/assets/admin/components/PatientCaseBanner.tsx b/assets/admin/components/PatientCaseBanner.tsx
index e0b146e8..cc0f9e38 100644
--- a/assets/admin/components/PatientCaseBanner.tsx
+++ b/assets/admin/components/PatientCaseBanner.tsx
@@ -1,7 +1,7 @@
-import { Link } from 'react-router-dom';
import { formatDate } from '../lib/utils';
+import BackButton from './ui/BackButton';
import {
- ArrowLeftPH, ArrowLeftD, FilesServicePhone, FilesServiceCalendar,
+ ArrowLeftD, FilesServicePhone, FilesServiceCalendar,
FilesServiceNotification, FilesServiceMessage,
} from './icons/FilesServiceIcons';
@@ -11,10 +11,7 @@ interface Tag { uuid: string; name: string; color: string }
export function Breadcrumb({ name, backTo }: { name: string; backTo: string }) {
return (
-
-
-
بازگشت
-
+
پرونده
diff --git a/assets/admin/components/ui/BackButton.test.tsx b/assets/admin/components/ui/BackButton.test.tsx
new file mode 100644
index 00000000..033859a9
--- /dev/null
+++ b/assets/admin/components/ui/BackButton.test.tsx
@@ -0,0 +1,90 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { screen, fireEvent, render } from '@testing-library/react';
+import { MemoryRouter, Route, Routes, Link } from 'react-router-dom';
+import BackButton from './BackButton';
+import PageHeader from './PageHeader';
+
+/**
+ * رفتار «بازگشت» باید در همهٔ صفحات یکی باشد: یک قدم عقب در تاریخچهٔ پنل، و وقتی
+ * صفحه مستقیم باز شده (تاریخچهای نیست) رفتن به صفحهٔ والد.
+ */
+function Detail({ fallback = '/admin/clinic-services' }: { fallback?: string }) {
+ return (
+ <>
+
صفحهٔ جزئیات
+
+ >
+ );
+}
+
+function List() {
+ return (
+ <>
+
صفحهٔ لیست
+
باز کردن جزئیات
+ >
+ );
+}
+
+function renderAt(initialEntries: string[]) {
+ return render(
+
+
+ } />
+ } />
+
+ ,
+ );
+}
+
+beforeEach(() => vi.clearAllMocks());
+
+describe('BackButton', () => {
+ it('ظاهر یکسان دارد: همان دکمهٔ بازگشتِ صفحهٔ سرویسها', () => {
+ renderAt(['/admin/clinic-services/x1']);
+
+ const btn = screen.getByRole('button', { name: /بازگشت/ });
+ expect(btn).toHaveClass('cp-btn-secondary');
+ expect(btn).toHaveAttribute('type', 'button');
+ });
+
+ it('وقتی از صفحهٔ دیگری آمدهایم، یک قدم به همان صفحه برمیگردد', () => {
+ renderAt(['/admin/clinic-services']);
+
+ fireEvent.click(screen.getByText('باز کردن جزئیات'));
+ expect(screen.getByText('صفحهٔ جزئیات')).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole('button', { name: /بازگشت/ }));
+ expect(screen.getByText('صفحهٔ لیست')).toBeInTheDocument();
+ });
+
+ it('در ورود مستقیم (بدون تاریخچه) به صفحهٔ والد میرود', () => {
+ renderAt(['/admin/clinic-services/x1']);
+
+ fireEvent.click(screen.getByRole('button', { name: /بازگشت/ }));
+
+ expect(screen.getByText('صفحهٔ لیست')).toBeInTheDocument();
+ });
+});
+
+describe('PageHeader — backTo', () => {
+ it('با backTo دکمهٔ بازگشت را نشان میدهد', () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByRole('button', { name: /بازگشت/ })).toBeInTheDocument();
+ });
+
+ it('بدون backTo دکمهای رندر نمیشود (صفحات سطحاول)', () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.queryByRole('button', { name: /بازگشت/ })).not.toBeInTheDocument();
+ });
+});
diff --git a/assets/admin/components/ui/BackButton.tsx b/assets/admin/components/ui/BackButton.tsx
new file mode 100644
index 00000000..91a772ab
--- /dev/null
+++ b/assets/admin/components/ui/BackButton.tsx
@@ -0,0 +1,23 @@
+import { ChevronRightIcon } from '@heroicons/react/24/outline';
+import { useGoBack } from '../../hooks/useGoBack';
+
+interface Props {
+ /** مقصد وقتی تاریخچهای برای برگشتن نیست (ورود مستقیم/رفرش) — معمولاً صفحهٔ لیستِ همان بخش. */
+ fallback: string;
+ label?: string;
+}
+
+/**
+ * دکمهٔ «بازگشت» — یک ظاهر و یک رفتار در همهٔ صفحاتی که از دل صفحهٔ دیگری باز میشوند.
+ * ظاهر مرجع، همان دکمهٔ بازگشتِ صفحهٔ سرویسهاست (`cp-btn-secondary` با ارتفاع ۳۶).
+ * در RTL، فلشِ «قبلی» راستسو است.
+ */
+export default function BackButton({ fallback, label = 'بازگشت' }: Props) {
+ const goBack = useGoBack(fallback);
+
+ return (
+
+ );
+}
diff --git a/assets/admin/components/ui/PageHeader.tsx b/assets/admin/components/ui/PageHeader.tsx
index fee2539a..2d720e96 100644
--- a/assets/admin/components/ui/PageHeader.tsx
+++ b/assets/admin/components/ui/PageHeader.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { ChevronLeftIcon } from '@heroicons/react/24/outline';
+import BackButton from './BackButton';
interface Crumb {
label: string;
@@ -12,12 +13,22 @@ interface Props {
breadcrumbs?: Crumb[];
action?: React.ReactNode;
description?: string;
+ /**
+ * صفحه از دل صفحهٔ دیگری باز میشود → دکمهٔ «بازگشت» بالای عنوان.
+ * مقدار، مقصدِ fallback است وقتی تاریخچهای برای برگشتن نیست.
+ */
+ backTo?: string;
}
-export default function PageHeader({ title, breadcrumbs, action, description }: Props) {
+export default function PageHeader({ title, breadcrumbs, action, description, backTo }: Props) {
return (
+ {backTo && (
+
+
+
+ )}
{breadcrumbs && breadcrumbs.length > 0 && (