feat(tour): add onboarding tours for various admin pages

- Integrated TourButton component into SettingsMenuPage, SkillsPage, SmsWalletPage, StaffPage, StaffSessionDetailPage, StaffTreatmentSessionsPage, SubscriptionPage, TagsSettingsPage, TreatmentCasesPage to enhance user onboarding experience.
- Created new tour definitions for appointments, clinics, staff management, financial management, and patient management, ensuring comprehensive guidance for users navigating the admin panel.
- Updated documentation to reflect the addition of tours and their implementation details.
This commit is contained in:
hamed
2026-08-10 10:10:20 +03:30
parent 20c8eaaad9
commit ecdefa3c24
73 changed files with 1008 additions and 76 deletions
+5 -3
View File
@@ -63,9 +63,11 @@ export default function DataTable<T extends object>({
<div>
{/* Toolbar row */}
{(onSearchChange !== undefined || headerExtra) && (
<div className="toolbar">
// لنگرهای data-tour اینجا تعریف می‌شوند تا هر صفحه‌ای که DataTable دارد
// بدون تغییر کد خودش، در تور راهنما قابل اشاره باشد.
<div className="toolbar" data-tour="page-toolbar">
{onSearchChange !== undefined && (
<div className="field" style={{ flex: '0 0 auto', minWidth: 240 }}>
<div className="field" data-tour="page-search" style={{ flex: '0 0 auto', minWidth: 240 }}>
<MagnifyingGlassIcon style={{ width: 16, height: 16, flexShrink: 0 }} />
<input
type="text"
@@ -79,7 +81,7 @@ export default function DataTable<T extends object>({
</div>
)}
<div className="table-wrap">
<div className="table-wrap" data-tour="page-table">
<table className="t">
<thead>
<tr>
+9 -4
View File
@@ -21,9 +21,14 @@ interface Props {
backTo?: string;
/** شناسهٔ تور راهنمای این صفحه؛ اگر در registry ثبت نشده باشد دکمه‌ای نمی‌آید. */
tourId?: string;
/**
* محتوای صفحه نشسته است و تورِ بار اول می‌تواند خودکار اجرا شود.
* صفحه‌ای که داده‌اش دیر می‌آید، `!isLoading` بدهد تا تور روی صفحهٔ نیمه‌ساخته نیفتد.
*/
tourReady?: boolean;
}
export default function PageHeader({ title, breadcrumbs, action, description, backTo, tourId }: Props) {
export default function PageHeader({ title, breadcrumbs, action, description, backTo, tourId, tourReady = true }: Props) {
return (
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 24 }}>
<div style={{ minWidth: 0 }}>
@@ -51,15 +56,15 @@ export default function PageHeader({ title, breadcrumbs, action, description, ba
))}
</nav>
)}
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }} data-tour="page-title">
<h1 className="section-title">{title}</h1>
<TourButton tourId={tourId} />
<TourButton tourId={tourId} ready={tourReady} />
</div>
{description && (
<p style={{ fontSize: 13, color: 'var(--text-3)', marginTop: 4 }}>{description}</p>
)}
</div>
{action && <div style={{ flexShrink: 0 }}>{action}</div>}
{action && <div style={{ flexShrink: 0 }} data-tour="page-action">{action}</div>}
</div>
);
}
+1 -1
View File
@@ -28,7 +28,7 @@ export default function Pagination({ page, total, limit, onPageChange }: Props)
}
return (
<div className="pagination">
<div className="pagination" data-tour="page-pagination">
<span style={{ fontSize: 12.5, color: 'var(--text-3)' }}>
نمایش {formatNumber(from)}{formatNumber(to)} از {formatNumber(total)} مورد
</span>
+9 -5
View File
@@ -8,17 +8,21 @@ import { getTour } from '../../lib/tour/registry';
* وجود تور قبل از هر هوکی بررسی می‌شود تا صفحه‌ای که راهنما ندارد — یعنی بیشتر
* صفحات پنل — هیچ درخواستی برای وضعیت تورها نفرستد.
*/
export default function TourButton({ tourId }: { tourId?: string }) {
export default function TourButton({ tourId, ready = false }: { tourId?: string; ready?: boolean }) {
const tour = getTour(tourId);
if (!tour) return null;
return <TourLauncher tourId={tour.id} />;
return <TourLauncher tourId={tour.id} ready={ready} />;
}
/** اجرای خودکار وظیفهٔ خود صفحه است؛ این دکمه فقط دستی اجرا می‌کند. */
function TourLauncher({ tourId }: { tourId: string }) {
const { start } = useTour(tourId);
/**
* `ready` یعنی محتوای صفحه نشسته است و اجرای خودکارِ بار اول مجاز است.
* صفحه‌ای که دکمه را مستقیم می‌گذارد و آماده‌بودنش را نمی‌داند، آن را false می‌گذارد
* و تور فقط با کلیک اجرا می‌شود.
*/
function TourLauncher({ tourId, ready }: { tourId: string; ready: boolean }) {
const { start } = useTour(tourId, { ready });
return (
<button