refactor: update pricing input handling to use PriceInput component

- Replaced raw input fields for pricing with PriceInput component across various forms and modals to ensure consistent formatting and accessibility.
- Updated tests to reflect changes in pricing input handling, ensuring values are displayed in toman with proper formatting.
- Enhanced accessibility by adding aria-labels and aria-invalid attributes to PriceInput components.
- Adjusted UI elements to improve layout and user experience, particularly in forms related to service items and scheduling.
- Changed labels from "نمایش در نوبت‌دهی" to "نمایش در نوبت‌دهی آنلاین" for clarity.
This commit is contained in:
hamed
2026-08-09 08:38:23 +03:30
parent e48ab9a974
commit b49abee52f
18 changed files with 299 additions and 96 deletions
@@ -160,7 +160,7 @@ export default function ServiceItemFormModal({ item, sectionUuid, onClose, onMan
size="md"
footer={
<>
<button type="button" className="btn" onClick={onClose}>انصراف</button>
<button type="button" className="btn secondary" onClick={onClose}>انصراف</button>
<button type="submit" form="service-item-form" className="btn primary" disabled={saving}>
{saving ? 'در حال ذخیره...' : 'ذخیره سرویس'}
</button>
@@ -183,6 +183,8 @@ export default function ServiceItemFormModal({ item, sectionUuid, onClose, onMan
)}
</div>
{/* دو عددِ کوتاه کنار هم؛ پیش از این «قیمت» با «پرسنل مسئول» هم‌ردیف بود و
با افزودن هر پرسنل، چیپ‌ها ستون را بلند می‌کردند و ردیف به‌هم می‌ریخت. */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div>
<label className="field-label">قیمت پایه (تومان) *</label>
@@ -200,16 +202,40 @@ export default function ServiceItemFormModal({ item, sectionUuid, onClose, onMan
)}
</div>
<div>
<label className="field-label">پرسنل مسئول</label>
<SearchableSelect
options={staffOptions}
value={''}
onChange={(v) => { if (v != null) form.setValue('staff_uuids', [...selectedStaffUuids, String(v)]); }}
placeholder="افزودن پرسنل (اختیاری)"
noOptionsMessage="پرسنلی باقی نمانده"
height={42}
/>
{selectedStaffUuids.length > 0 && (
<label className="field-label">زمان متوسط (دقیقه)</label>
<div className="field">
<input {...numericField(form.register('duration_minutes'))} placeholder="مثلاً: 50" />
</div>
</div>
</div>
<div>
<label className="field-label">نوع خدمت *</label>
<SearchableSelect
options={categories.map((c) => ({ value: c.key, label: c.label }))}
value={form.watch('service_category') || null}
onChange={(v) => { if (v != null) form.setValue('service_category', String(v)); }}
placeholder="انتخاب نوع خدمت"
noOptionsMessage="نوعی تعریف نشده است"
height={42}
/>
<span style={{ display: 'block', marginTop: 6, fontSize: 11.5, color: 'var(--text-3)' }}>
درصد پوشش بیمه بر اساس همین نوع محاسبه میشود.
</span>
</div>
{/* تمام‌عرض: چیپ‌های پرسنل چند سطر می‌شوند و در نیمْ‌ستون می‌شکستند. */}
<div>
<label className="field-label">پرسنل مسئول</label>
<SearchableSelect
options={staffOptions}
value={''}
onChange={(v) => { if (v != null) form.setValue('staff_uuids', [...selectedStaffUuids, String(v)]); }}
placeholder="افزودن پرسنل (اختیاری)"
noOptionsMessage="پرسنلی باقی نمانده"
height={42}
/>
{selectedStaffUuids.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 8 }}>
{selectedStaffUuids.map((uuid) => (
<span key={uuid} style={{
@@ -229,39 +255,20 @@ export default function ServiceItemFormModal({ item, sectionUuid, onClose, onMan
))}
</div>
)}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, alignItems: 'end' }}>
<div>
<label className="field-label">زمان متوسط (دقیقه)</label>
<div className="field">
<input {...numericField(form.register('duration_minutes'))} placeholder="مثلاً: 50" />
</div>
</div>
<div style={{ padding: '9px 0' }}>
<Switch
inline
checked={form.watch('bookable') ?? false}
onChange={(v) => form.setValue('bookable', v, { shouldDirty: true })}
label="نمایش در نوبت‌دهی"
/>
</div>
</div>
<div>
<label className="field-label">نوع خدمت *</label>
<SearchableSelect
options={categories.map((c) => ({ value: c.key, label: c.label }))}
value={form.watch('service_category') || null}
onChange={(v) => { if (v != null) form.setValue('service_category', String(v)); }}
placeholder="انتخاب نوع خدمت"
noOptionsMessage="نوعی تعریف نشده است"
height={42}
{/* تصمیمِ «آنلاین رزرو بشود یا نه» یک تنظیم است، نه یک فیلد فرم؛ پیش از این
در نیمهٔ خالیِ ردیفِ «زمان متوسط» با پدینگ دستی هم‌تراز شده بود. */}
<div style={{
padding: '12px 13px', borderRadius: 'var(--r-sm)',
border: '1px solid var(--border)', background: 'var(--surface-2)',
}}>
<Switch
checked={form.watch('bookable') ?? false}
onChange={(v) => form.setValue('bookable', v, { shouldDirty: true })}
label="نمایش در نوبت‌دهی آنلاین"
hint="با فعال بودن این گزینه، بیمار می‌تواند این سرویس را به‌صورت آنلاین رزرو کند."
/>
<span style={{ display: 'block', marginTop: 6, fontSize: 11.5, color: 'var(--text-3)' }}>
درصد پوشش بیمه بر اساس همین نوع محاسبه میشود.
</span>
</div>
<div>
@@ -330,7 +337,7 @@ export default function ServiceItemFormModal({ item, sectionUuid, onClose, onMan
{editing && onManageInsurance && (
<button
type="button"
className="btn sm"
className="btn secondary sm"
style={{ flexShrink: 0 }}
onClick={() => { const it = editing; onClose(); onManageInsurance(it); }}
>