Files
clinicpro/assets/admin/pages/RecordNumberSettingsPage.tsx
T
hamed ecdefa3c24 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.
2026-08-10 10:10:20 +03:30

151 lines
6.9 KiB
TypeScript

import { useEffect, useState } from 'react';
import SettingsLayout from '../components/layout/SettingsLayout';
import SearchableSelect from '../components/ui/SearchableSelect';
import { useRecordNumberSettings } from '../hooks/useRecordNumberSettings';
import type { RecordNumberResetPolicy } from '../hooks/useRecordNumberSettings';
import Switch from '../components/ui/Switch';
import TourButton from '../components/ui/TourButton';
const RESET_OPTIONS: { value: RecordNumberResetPolicy; label: string }[] = [
{ value: 'none', label: 'هرگز — شمارنده پیوسته جلو می‌رود' },
{ value: 'yearly', label: 'هر سال شمسی از ۱ شروع شود' },
{ value: 'monthly', label: 'هر ماه شمسی از ۱ شروع شود' },
];
const TOKENS: [string, string][] = [
['{SEQ}', 'شمارنده — {SEQ:4} یعنی چهار رقمی با صفرِ ابتدایی'],
['{YY}', 'دو رقم آخر سال شمسی'],
['{YYYY}', 'سال شمسی کامل'],
['{MM}', 'ماه شمسی دو رقمی'],
];
/**
* شمارهٔ پرونده — الگوی شماره‌گذاری خودکار پرونده‌های همین مجموعه.
*
* پیش‌نمایش سمت کلاینت ساخته نمی‌شود: همان `next_preview` سرور نشان داده می‌شود تا
* دو پیاده‌سازیِ رندر (اینجا و در `RecordNumberGenerator`) از هم واگرا نشوند.
*/
export default function RecordNumberSettingsPage() {
const { settings, loading, save } = useRecordNumberSettings();
const [enabled, setEnabled] = useState(false);
const [pattern, setPattern] = useState('');
const [resetPolicy, setResetPolicy] = useState<RecordNumberResetPolicy>('none');
useEffect(() => {
if (!settings) return;
setEnabled(settings.enabled);
setPattern(settings.pattern);
setResetPolicy(settings.reset_policy);
}, [settings]);
const readOnly = !settings?.can_edit;
const dirty = !!settings && (
enabled !== settings.enabled || pattern !== settings.pattern || resetPolicy !== settings.reset_policy
);
return (
<SettingsLayout active="record-number">
<div
style={{ background: 'var(--surface)', minWidth: 0 }}
className="px-4 py-4 md:px-6 md:py-6 rounded-[var(--r-lg)]"
>
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }} data-tour="page-title"><h1 className="section-title" style={{ marginBottom: 6 }}>شماره پرونده</h1><TourButton tourId="record-number-settings" ready /></div>
<p style={{ fontSize: 12.5, color: 'var(--text-3)', marginBottom: 18, lineHeight: 2 }}>
با روشن‌کردن این گزینه، شمارهٔ هر پروندهٔ تازه چه از فرم تشکیل پرونده و چه از
نوبتی که قطعی می‌شود بر اساس همین الگو ساخته می‌شود.
</p>
{loading ? (
<div className="space-y-2">{Array.from({ length: 3 }).map((_, i) => <div key={i} className="h-12 rounded-xl skeleton" />)}</div>
) : !settings ? (
<div className="card" style={{ padding: 24, textAlign: 'center', color: 'var(--text-3)', fontSize: 13.5 }}>
دسترسی به تنظیمات شمارهٔ پرونده ندارید.
</div>
) : (
<>
{readOnly && (
<div style={{
marginBottom: 16, padding: '10px 14px', borderRadius: 'var(--r-sm)',
background: 'var(--warning-bg)', color: 'var(--text-2)', fontSize: 12.5,
}}>
تغییر الگو فقط با حساب صاحب مجموعه ممکن است؛ اینجا فقط مقدار فعلی را می‌بینید.
</div>
)}
<Switch
inline
checked={enabled}
disabled={readOnly}
onChange={setEnabled}
label="شماره‌گذاری خودکار پرونده"
/>
<div style={{ maxWidth: 420, marginTop: 18 }}>
<label className="field-label" htmlFor="record-number-pattern">الگوی شماره</label>
<div className="field" style={{ marginTop: 6 }}>
<input
id="record-number-pattern"
value={pattern}
dir="ltr"
readOnly={readOnly}
onChange={(e) => setPattern(e.target.value)}
placeholder="MD-{YY}-{SEQ:4}"
/>
</div>
<ul style={{ margin: '10px 0 0', padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 4 }}>
{TOKENS.map(([token, hint]) => (
<li key={token} style={{ fontSize: 12, color: 'var(--text-3)' }}>
<code dir="ltr" style={{ color: 'var(--primary)' }}>{token}</code> {hint}
</li>
))}
</ul>
</div>
<div style={{ maxWidth: 420, marginTop: 18 }}>
<label className="field-label" id="reset-policy-label">ریست شمارنده</label>
<div style={{ marginTop: 6 }}>
<SearchableSelect
options={RESET_OPTIONS}
value={resetPolicy}
onChange={(v) => setResetPolicy((v ?? 'none') as RecordNumberResetPolicy)}
ariaLabelledBy="reset-policy-label"
isDisabled={readOnly}
height={44}
/>
</div>
</div>
<div style={{
marginTop: 18, padding: '12px 14px', borderRadius: 'var(--r-sm)',
background: 'var(--surface-2)', border: '1px solid var(--border)', maxWidth: 420,
}}>
<span style={{ fontSize: 12.5, color: 'var(--text-2)' }}>شمارهٔ بعدی: </span>
<b dir="ltr" style={{ fontSize: 14, color: 'var(--text)' }}>{settings.next_preview}</b>
<div style={{ fontSize: 11.5, color: 'var(--text-3)', marginTop: 6 }}>
{dirty
? 'پیش‌نمایش پس از ذخیره به‌روز می‌شود.'
: `شمارنده تا اینجا ${settings.counter} پرونده را شماره‌گذاری کرده است.`}
</div>
</div>
{!readOnly && (
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 22 }}>
<button
type="button"
className="btn primary"
disabled={!dirty || save.isPending}
onClick={() => save.mutate({ enabled, pattern, reset_policy: resetPolicy })}
>
{save.isPending ? 'در حال ذخیره…' : 'ذخیره تنظیمات'}
</button>
</div>
)}
</>
)}
</div>
</SettingsLayout>
);
}