Initialize the admin app with banner management for app sections.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-09 17:08:46 +03:30
co-authored by Cursor
commit d670921f74
104 changed files with 26137 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
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}`
}