feat: enhance StaffPage modals and form fields with improved layout and error handling

This commit is contained in:
hamed
2026-07-30 10:30:39 +03:30
parent 57aeb40934
commit 1d338503c8
2 changed files with 121 additions and 55 deletions
+118 -55
View File
@@ -235,32 +235,47 @@ export default function StaffPage() {
</div>
{/* Modal ایجاد */}
<Modal open={createOpen} onClose={() => setCreateOpen(false)} title="افزودن پرسنل جدید">
<form onSubmit={createForm.handleSubmit((d) => createMutation.mutate(d))}>
<StaffFormFields form={createForm} />
<div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
<button type="submit" className="btn primary" disabled={createMutation.isPending}>
{createMutation.isPending ? 'در حال ذخیره...' : 'ذخیره'}
<Modal
open={createOpen}
onClose={() => setCreateOpen(false)}
title="افزودن پرسنل جدید"
size="lg"
footer={
<>
<button type="button" className="btn ghost sm" onClick={() => setCreateOpen(false)}>انصراف</button>
<button form="staff-create-form" type="submit" className="btn primary sm" disabled={createMutation.isPending}>
{createMutation.isPending ? 'در حال ذخیره...' : 'ذخیره پرسنل'}
</button>
<button type="button" className="btn" onClick={() => setCreateOpen(false)}>انصراف</button>
</div>
</>
}
>
<form id="staff-create-form" onSubmit={createForm.handleSubmit((d) => createMutation.mutate(d))}>
<StaffFormFields form={createForm} mode="create" />
</form>
</Modal>
{/* Modal ویرایش */}
<Modal open={!!editTarget} onClose={() => setEditTarget(null)} title="ویرایش پرسنل">
<Modal
open={!!editTarget}
onClose={() => setEditTarget(null)}
title={editTarget ? `ویرایش — ${editTarget.full_name}` : 'ویرایش پرسنل'}
size="lg"
footer={
<>
<button type="button" className="btn ghost sm" onClick={() => setEditTarget(null)}>انصراف</button>
<button form="staff-edit-form" type="submit" className="btn primary sm" disabled={editMutation.isPending}>
{editMutation.isPending ? 'در حال ذخیره...' : 'ذخیره تغییرات'}
</button>
</>
}
>
<form
id="staff-edit-form"
onSubmit={editForm.handleSubmit((d) =>
editTarget && editMutation.mutate({ uuid: editTarget.uuid, body: d })
)}
>
<StaffFormFields form={editForm} />
<div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
<button type="submit" className="btn primary" disabled={editMutation.isPending}>
{editMutation.isPending ? 'در حال ذخیره...' : 'ذخیره'}
</button>
<button type="button" className="btn" onClick={() => setEditTarget(null)}>انصراف</button>
</div>
<StaffFormFields form={editForm} mode="edit" />
</form>
</Modal>
@@ -278,46 +293,94 @@ export default function StaffPage() {
);
}
function StaffFormFields({ form }: { form: ReturnType<typeof useForm<StaffFormData>> }) {
const { register, formState: { errors } } = form;
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div className="field">
<label>نام و نام خانوادگی *</label>
<input {...register('full_name')} placeholder="علی رضایی" />
{errors.full_name && <span className="field-error">{errors.full_name.message}</span>}
</div>
<div className="field">
<label>سمت</label>
<input {...register('job_title')} placeholder="پرستار" />
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div className="field">
<label>موبایل (نام کاربری ورود) *</label>
<input {...numericField(register('phone'), 11)} placeholder="09121234567" />
{errors.phone && <span className="field-error">{errors.phone.message}</span>}
</div>
<div className="field">
<label>کد ملی</label>
<input {...numericField(register('national_code'), 10)} placeholder="0012345678" />
</div>
</div>
<div className="field">
<label>آدرس</label>
<input {...register('address')} placeholder="آدرس محل سکونت" />
</div>
const Required = () => <span style={{ color: 'var(--danger)' }}>*</span>;
<div className="field" style={{ borderTop: '1px solid var(--border)', paddingTop: 12 }}>
<label>رمز عبور ورود به پنل</label>
<input type="password" autoComplete="new-password" {...register('password')} placeholder="حداقل ۸ کاراکتر — خالی یعنی بدون تغییر" />
{errors.password && <span className="field-error">{errors.password.message}</span>}
<div style={{ fontSize: 12, color: 'var(--text-3)', marginTop: 4 }}>
برای هر پرسنل حساب کاربری ساخته می‌شود: با همین شماره موبایل وارد پنل میشود و فقط
داشبورد و سرویسهای خودش را میبیند.
</div>
/** تیتر یک بخش از فرم — همان الگوی سایر فرم‌های چندبخشی پنل. */
function FormSection({ title, hint, children }: { title: string; hint?: string; children: React.ReactNode }) {
return (
<section style={{ marginBottom: 22 }}>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 12 }}>
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)' }}>{title}</h3>
{hint && <span style={{ fontSize: 12, color: 'var(--text-3)' }}>{hint}</span>}
</div>
</div>
{children}
</section>
);
}
function StaffFormFields({ form, mode }: { form: ReturnType<typeof useForm<StaffFormData>>; mode: 'create' | 'edit' }) {
const { register, formState: { errors } } = form;
return (
<>
<FormSection title="مشخصات پرسنل">
<div className="grid-2" style={{ gap: 14, marginBottom: 0 }}>
<div className="form-row" style={{ marginBottom: 0 }}>
<label>نام و نام خانوادگی <Required /></label>
<input {...register('full_name')} className={`input ${errors.full_name ? 'err' : ''}`} placeholder="علی رضایی" />
{errors.full_name && <p className="err-text">{errors.full_name.message}</p>}
</div>
<div className="form-row" style={{ marginBottom: 0 }}>
<label>سمت</label>
<input {...register('job_title')} className="input" placeholder="پرستار" />
</div>
<div className="form-row" style={{ marginBottom: 0 }}>
<label>کد ملی</label>
<input {...numericField(register('national_code'), 10)} className="input" dir="ltr" inputMode="numeric" placeholder="0012345678" />
</div>
<div className="form-row" style={{ marginBottom: 0 }}>
<label>آدرس</label>
<input {...register('address')} className="input" placeholder="آدرس محل سکونت" />
</div>
</div>
</FormSection>
<FormSection title="حساب کاربری ورود به پنل" hint="برای هر پرسنل ساخته می‌شود">
<div
style={{
background: 'var(--primary-soft)',
border: '1px solid var(--border)',
borderRadius: 'var(--r)',
padding: 16,
}}
>
<p style={{ fontSize: 12.5, color: 'var(--text-2)', lineHeight: 1.9, marginBottom: 14 }}>
پرسنل با همین شمارهٔ موبایل وارد پنل میشود و فقط داشبورد و سرویسهای خودش را میبیند.
</p>
<div className="grid-2" style={{ gap: 14, marginBottom: 0 }}>
<div className="form-row" style={{ marginBottom: 0 }}>
<label>موبایل نام کاربری <Required /></label>
<input
{...numericField(register('phone'), 11)}
className={`input ${errors.phone ? 'err' : ''}`}
dir="ltr"
inputMode="numeric"
autoComplete="off"
placeholder="09121234567"
/>
{errors.phone && <p className="err-text">{errors.phone.message}</p>}
</div>
<div className="form-row" style={{ marginBottom: 0 }}>
<label>رمز عبور</label>
<input
type="password"
autoComplete="new-password"
{...register('password')}
className={`input ${errors.password ? 'err' : ''}`}
dir="ltr"
placeholder={mode === 'edit' ? 'خالی = بدون تغییر' : 'حداقل ۸ کاراکتر'}
/>
{errors.password
? <p className="err-text">{errors.password.message}</p>
: <p style={{ fontSize: 11.5, color: 'var(--text-3)' }}>
{mode === 'edit'
? 'برای تغییر رمز، مقدار جدید را وارد کنید.'
: 'خالی بگذارید تا پرسنل خودش با «فراموشی رمز» تعیین کند.'}
</p>}
</div>
</div>
</div>
</FormSection>
</>
);
}
+3
View File
@@ -579,6 +579,9 @@ body {
/* field-label utility */
.field-label { display: block; font-size: 12.5px; font-weight: 600; color: var(--text-2); margin-bottom: 5px; }
/* پیام خطای اعتبارسنجی زیر ورودی — هر دو نام در صفحات موجود استفاده شده‌اند */
.err-text, .field-error { display: block; font-size: 11.5px; font-weight: 600; color: var(--danger); }
/* ── Buttons ─────────────────────────────────────────────────── */
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;