Add tests and implementation for ServiceDetailPage and PriceInput components

- Implement PriceInput component tests to validate Persian and Arabic numeral handling, input formatting, and controlled behavior.
- Create ServiceDetailPage component with detailed service information, including pricing, insurance coverage, and editing capabilities.
- Add API tests for service item detail retrieval and coverage synchronization with insurance contracts.
- Ensure proper error handling and user feedback for service item retrieval and coverage management.
This commit is contained in:
hamed
2026-07-18 12:10:49 +03:30
parent f1d58e1604
commit 42d9ad26c5
27 changed files with 1758 additions and 284 deletions
+27
View File
@@ -14,6 +14,11 @@ export function formatNumber(n: number): string {
return new Intl.NumberFormat('fa-IR').format(n);
}
// سال شمسی: رقم فارسی بدون جداکننده‌ی هزارگان («۱۴۰۴» نه «۱٬۴۰۴»).
export function formatYear(year: number): string {
return new Intl.NumberFormat('fa-IR', { useGrouping: false }).format(year);
}
// تایم‌زون رسمی سراسری برنامه = ایران. همهٔ نمایش/تبدیل تاریخ باید با این tz باشد،
// مستقل از تایم‌زون مرورگرِ کاربر (اجباری).
export const APP_TZ = 'Asia/Tehran';
@@ -122,6 +127,28 @@ export function sanitizeMobileInput(input: string): string {
return digitsOnly(input, 11);
}
/**
* رشته‌ی ورودی کاربر (ارقام فارسی/عربی، جداکننده‌ی هزارگان، فاصله) را به عدد امن تبدیل می‌کند.
* هرگز NaN برنمی‌گرداند؛ ورودی نامعتبر یا خالی ⇒ null.
*/
export function parseUserNumber(raw: string | number | null | undefined): number | null {
if (raw == null || raw === '') return null;
const s = toEnglishDigits(String(raw)).replace(/[,\s٫٬]/g, '');
if (!/^-?(\d+\.?\d*|\.\d+)$/.test(s)) return null;
const n = Number(s);
return Number.isFinite(n) ? n : null;
}
/** همان parseUserNumber با محدودکردن بازه — برای درصد (۰..۱۰۰) و مقادیر غیرمنفی. */
export function parseUserNumberClamped(
raw: string | number | null | undefined,
min: number,
max: number,
): number | null {
const n = parseUserNumber(raw);
return n == null ? null : Math.min(max, Math.max(min, n));
}
// z.coerce.number() روی رشته‌ی فارسی NaN می‌دهد. فیلدهای عددی پنل در مبدأ (numericField
// در lib/forms.ts) نرمال می‌شوند، پس این wrapper فقط برای مصرف‌کننده‌های خارج از آن مسیر است.
// روی resolverهای React Hook Form استفاده نکن — z.preprocess تایپ ورودی را unknown می‌کند.