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
@@ -1,14 +1,16 @@
import React, { useEffect, useState } from 'react';
import PriceInput from '../ui/PriceInput';
import SearchableSelect from '../ui/SearchableSelect';
import Switch from '../ui/Switch';
import { formatRial } from '../../lib/utils';
import { formatRial, rialToToman, tomanToRial } from '../../lib/utils';
import type { ClinicResource, ResourceServiceOffering, ServiceItemOption } from '../../types';
type Line = {
service_uuid: string;
service_name: string;
duration_minutes: string;
price_rials: string;
/** مقدارِ داخل فیلد به **تومان** است؛ تبدیل به ریال فقط موقع ذخیره انجام می‌شود. */
price_tomans: string;
active: boolean;
effective_duration_minutes: number | null;
effective_price_rials: number;
@@ -63,7 +65,9 @@ export default function ResourceServicesPanel({
service_uuid: o.service_uuid,
service_name: o.service_name,
duration_minutes: o.duration_minutes === null ? '' : String(o.duration_minutes),
price_rials: o.price_rials === null ? '' : String(o.price_rials),
// ورودی به تومان است تا با placeholder و بقیهٔ پنل یکی باشد؛ پیش از این
// برچسب «ریال» بود و placeholder تومان — یعنی ده برابر اختلاف در یک فیلد.
price_tomans: o.price_rials === null ? '' : String(rialToToman(o.price_rials)),
active: o.active,
effective_duration_minutes: o.effective_duration_minutes,
effective_price_rials: o.effective_price_rials,
@@ -142,12 +146,12 @@ export default function ResourceServicesPanel({
</div>
<div className="field-block" style={{ flex: 1 }}>
<label style={{ fontSize: 12, color: 'var(--text-2)' }}>قیمت (ریال)</label>
<input
<label style={{ fontSize: 12, color: 'var(--text-2)' }}>قیمت (تومان)</label>
<PriceInput
className="field"
inputMode="numeric"
value={line.price_rials}
onChange={(e) => patch(index, { price_rials: e.target.value })}
ariaLabel={`قیمت ${line.service_name} (تومان)`}
value={line.price_tomans === '' ? '' : Number(line.price_tomans)}
onChange={(v) => patch(index, { price_tomans: v === 0 ? '' : String(v) })}
placeholder={`${formatRial(line.effective_price_rials)}${SOURCE_LABELS[line.price_source] ?? '—'}`}
/>
</div>
@@ -172,8 +176,11 @@ export default function ResourceServicesPanel({
{
service_uuid: picked.uuid,
service_name: picked.name,
duration_minutes: '',
price_rials: '',
// سرویسِ تازه با پیش‌فرض خودش پر می‌شود تا کاربر عددی جلوی چشمش
// باشد و لازم نباشد از placeholder رونویسی کند. پاک‌کردنش همان
// «ارث از سرویس» را برمی‌گرداند؛ ردیف‌های موجود دست نمی‌خورند.
duration_minutes: picked.duration_minutes == null ? '' : String(picked.duration_minutes),
price_tomans: picked.price_rials == null ? '' : String(rialToToman(picked.price_rials)),
active: true,
effective_duration_minutes: picked.duration_minutes ?? null,
effective_price_rials: picked.price_rials ?? 0,
@@ -189,8 +196,9 @@ export default function ResourceServicesPanel({
)}
<p style={{ fontSize: 12, color: 'var(--text-3)', margin: 0 }}>
خالی گذاشتن مدت یا قیمت یعنی همان مقدارِ سطح بالاتر استفاده شود. ذخیره کل فهرست را
جایگزین میکند؛ سرویسی که اینجا نباشد از این منبع برداشته میشود.
سرویس تازه با مدت و قیمت پیشفرض خودش پر میشود؛ میتوانید عوضش کنید. خالی گذاشتن
مدت یا قیمت یعنی همان مقدارِ سطح بالاتر استفاده شود. ذخیره کل فهرست را جایگزین
میکند؛ سرویسی که اینجا نباشد از این منبع برداشته میشود.
</p>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
@@ -203,12 +211,16 @@ export default function ResourceServicesPanel({
disabled={saving}
onClick={() =>
onSave(
lines.map((l) => ({
service_uuid: l.service_uuid,
duration_minutes: l.duration_minutes.trim(),
price_rials: l.price_rials.trim(),
active: l.active,
})),
lines.map((l) => {
const toman = l.price_tomans.trim();
return {
service_uuid: l.service_uuid,
duration_minutes: l.duration_minutes.trim(),
// قرارداد سرور ریال است؛ رشتهٔ خالی یعنی «ارث» و نباید صفر شود.
price_rials: toman === '' ? '' : String(tomanToRial(Number(toman))),
active: l.active,
};
}),
)
}
>