const persianDigits = '۰۱۲۳۴۵۶۷۸۹' const numberFormatter = new Intl.NumberFormat('fa-IR', { useGrouping: true }) export function normalizeDigits(input: string): string { return input.replace(/[۰-۹]/g, (digit) => String(persianDigits.indexOf(digit))) } export function extractNumericString(input: string): string { return normalizeDigits(input).replace(/[^\d]/g, '') } export function parseFormattedNumber(value: string): number { const numeric = extractNumericString(value) return numeric ? Number(numeric) : 0 } export function formatNumber(value: number): string { if (!Number.isFinite(value)) return '—' return numberFormatter.format(value) } export function formatCurrency(value: number): string { return `${formatNumber(value)} تومان` } export function formatMeterage(value: number): string { return `${formatNumber(value)} متر` } export function toPersianDigits(input: string | number): string { return String(input).replace(/\d/g, (d) => persianDigits[Number(d)]!) } const persianDateFormatter = new Intl.DateTimeFormat('fa-IR-u-ca-persian', { year: 'numeric', month: '2-digit', day: '2-digit', }) const persianDateTimeFormatter = new Intl.DateTimeFormat('fa-IR-u-ca-persian', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }) /** Detect already-Jalali date strings like 1403/07/30 or 1403-07-30 */ function isJalaliDateString(value: string): boolean { const normalized = normalizeDigits(value.trim()).replace(/-/g, '/') const match = normalized.match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})/) if (!match) return false const year = Number(match[1]) return year >= 1300 && year <= 1500 } function toDateValue(date: string | Date): Date { if (typeof date !== 'string') return date const trimmed = date.trim() // فقط تاریخِ بدون ساعت (YYYY-MM-DD) را به‌صورت تقویم محلی بخوان تا شیفت UTC روز را جابه‌جا نکند. // رشته‌های دارای ساعت (…T10:30:00 یا … 10:30) باید کامل پارس شوند. const dateOnly = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/) if (dateOnly && Number(dateOnly[1]) >= 1700) { return new Date(Number(dateOnly[1]), Number(dateOnly[2]) - 1, Number(dateOnly[3])) } const parsed = new Date(trimmed) if (Number.isNaN(parsed.getTime())) return new Date(NaN) return parsed } export function formatDate(date: string | Date | null | undefined): string { if (!date) return '—' if (typeof date === 'string' && isJalaliDateString(date)) { const normalized = normalizeDigits(date.trim()).replace(/-/g, '/') const match = normalized.match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})/) if (!match) return toPersianDigits(normalized) const [, y, m, d] = match return toPersianDigits(`${y}/${m!.padStart(2, '0')}/${d!.padStart(2, '0')}`) } const value = toDateValue(date) if (Number.isNaN(value.getTime())) return '—' return persianDateFormatter.format(value) } export function formatDateTime(date: string | Date | null | undefined): string { if (!date) return '—' if (typeof date === 'string' && isJalaliDateString(date) && !/[T\s]\d/.test(date)) { return formatDate(date) } const value = toDateValue(date) if (Number.isNaN(value.getTime())) return '—' return persianDateTimeFormatter.format(value) } export function generateOrderNumber(): string { const year = new Intl.DateTimeFormat('fa-IR-u-ca-persian', { year: 'numeric' }).format(new Date()) const persianYear = year.replace(/[^\d]/g, '') || '1403' const seq = Math.floor(Math.random() * 9000) + 1000 return `ORD-${persianYear}-${seq}` }