added some text handling 3

This commit is contained in:
2026-07-08 18:24:55 +03:30
parent 89e2643dff
commit 01437afa84
19 changed files with 683 additions and 168 deletions
+36
View File
@@ -1,5 +1,7 @@
/** توضیحات ساده فارسی برای فرم‌های ادمین — قابل فهم بدون دانش فنی */
import { adminHelp } from '@/utils/adminHelpFormat'
export const bannersPageIntro =
'بنرها همان تصاویر اسلایدر بالای صفحهٔ اصلی اپ مشتری هستند. هر کارت = یک اسلاید. بعد از هر تغییر حتماً «ذخیره تغییرات» را بزنید؛ تا آن لحظه در اپ دیده نمی‌شود.'
@@ -65,3 +67,37 @@ export const discountExpiryHelp =
export const priceTriggersPageIntro =
'لیست هشدارهای قیمت مشتریان. می‌توانید هشدار جدید بسازید، ویرایش کنید، فعال/غیرفعال کنید یا حذف کنید. وقتی قیمت به هدف برسد، به مشتری پیامک یا ایمیل می‌رود و هشدار خودکار غیرفعال می‌شود.'
export const physicalWithdrawPageIntro =
'درخواست تحویل فیزیکی طلا یا محصول از کیف مشتری. درخواست‌های ثبت‌شده از اپ مشتری و همین‌جا توسط ادمین در این لیست دیده می‌شوند. با ثبت درخواست، مقدار از کیف مشتری کسر می‌شود. در وضعیت «در انتظار ارسال» با آیکون ویرایش می‌توانید مقدار را (حتی اعشاری برای آبشده) تنظیم کنید؛ با «تایید» به «ارسال شده» می‌رود. برای برداشت ریالی از منوی «برداشت و واریز» استفاده کنید.'
export const physicalWithdrawCustomerHelp =
'مشتری‌ای که می‌خواهید برایش تحویل فیزیکی ثبت کنید. بعد از انتخاب، لیست محصولات و آدرس‌های همان مشتری بارگذاری می‌شود.'
export const physicalWithdrawAddressHelp =
'آدرسی که محصول به آن ارسال می‌شود. مشتری آدرس‌ها را در اپ خود ثبت می‌کند؛ اگر آدرسی ندارد، ابتدا از پنل مشتریان یا از خود مشتری بخواهید آدرس اضافه کند.'
export const physicalWithdrawProductHelp =
'محصولی که مشتری می‌خواهد فیزیکی تحویل بگیرد. فقط محصولاتی با موجودی بیشتر از صفر نشان داده می‌شوند.'
export const physicalWithdrawAmountHelp = adminHelp(
'تعداد یا مقدار درخواستی همان واحدی است که در اپ مشتری نمایش داده می‌شود (مثلاً تعداد سکه یا گرم).',
'مقدار درخواستی ≤ موجودی کیف مشتری'
)
export const physicalWithdrawDecreaseHelp =
'یک واحد از مقدار این خط کم می‌کند و همان مقدار به کیف مشتری برمی‌گردد. فقط وقتی وضعیت «در انتظار ارسال» است.'
export const physicalWithdrawIncreaseHelp =
'یک واحد به مقدار این خط اضافه می‌کند و از کیف مشتری کسر می‌شود. فقط وقتی وضعیت «در انتظار ارسال» است و موجودی کافی باشد.'
export const physicalWithdrawAdjustValueHelp = adminHelp(
'مقدار نهایی برداشت را وارد کنید. برای آبشده و محصولات وزنی می‌توانید اعشار بزنید (مثلاً ۱.۲۵۰ گرم). برای سکه و محصولات تعدادی فقط عدد صحیح مجاز است.',
'مقدار نهایی ≤ موجودی کیف مشتری'
)
export const physicalWithdrawApproveHelp =
'درخواست را تایید می‌کند و وضعیت به «ارسال شده» تغییر می‌کند؛ یعنی آمادهٔ ارسال یا تحویل است.'
export const physicalWithdrawStatusHelp =
'پیگیری مراحل ارسال: در انتظار ارسال، ارسال شده، لغو شده، یا در انتظار تعیین مجدد قیمت (برای محصولاتی که وزن نهایی مشخص نیست).'
+40
View File
@@ -0,0 +1,40 @@
/** ساختار متن راهنما — توضیح + فرمول اختیاری در کادر جدا */
export type AdminHelpContent = {
description: string
formula?: string
}
const FORMULA_MARKER = '\n\n__FORMULA__\n'
export function adminHelp(description: string, formula?: string): string {
const body = description.trim()
if (!formula?.trim()) return body
return `${body}${FORMULA_MARKER}${formula.trim()}`
}
export function parseAdminHelp(input: string | AdminHelpContent | undefined | null): AdminHelpContent {
if (!input) return { description: '' }
if (typeof input !== 'string') {
return {
description: input.description?.trim() ?? '',
formula: input.formula?.trim() || undefined,
}
}
const marked = input.split(FORMULA_MARKER)
if (marked.length === 2) {
return {
description: marked[0].trim(),
formula: marked[1].trim() || undefined,
}
}
return { description: input.trim() }
}
export function adminHelpPlainText(content: AdminHelpContent): string {
const parts = [content.description].filter(Boolean)
if (content.formula) parts.push(content.formula)
return parts.join('\n\n')
}
+66 -11
View File
@@ -1,20 +1,69 @@
const PERSIAN_DIGITS = '۰۱۲۳۴۵۶۷۸۹'
const ARABIC_DIGITS = '٠١٢٣٤٥٦٧٨٩'
function toAsciiDigits(value: string): string {
return value
.replace(/[۰-۹]/g, (d) => String(PERSIAN_DIGITS.indexOf(d)))
.replace(/[٠-٩]/g, (d) => String(ARABIC_DIGITS.indexOf(d)))
}
/** پارس مبلغ محدودیت سفارش — ارقام فارسی/عربی و جداکننده هزارگان */
export function parseOrderLimitCashValue(value: unknown): number | undefined {
if (value === undefined || value === null || value === '') {
return undefined
}
const raw = toAsciiDigits(String(value).trim().replace(/[,\s٬]/g, ''))
if (raw === '') {
return undefined
}
const num = Number(raw)
if (!Number.isFinite(num) || num < 0) {
return undefined
}
return Math.trunc(num)
}
function orderLimitInputsPresent(form: {
min_order_cash?: number | string | null
max_order_cash?: number | string | null
}): boolean {
return (
(form.min_order_cash != null && form.min_order_cash !== '') ||
(form.max_order_cash != null && form.max_order_cash !== '')
)
}
export function validateProductOrderLimitForm(form: {
order_limit_enabled?: boolean
min_order_cash?: number | string | null
max_order_cash?: number | string | null
}): string | null {
if (!form.order_limit_enabled) return null
const enabled = !!form.order_limit_enabled || orderLimitInputsPresent(form)
if (!enabled) return null
const min = form.min_order_cash
const max = form.max_order_cash
const hasMin = min != null && min !== ''
const hasMax = max != null && max !== ''
const rawMin = form.min_order_cash
const rawMax = form.max_order_cash
const hasMinInput = rawMin != null && rawMin !== ''
const hasMaxInput = rawMax != null && rawMax !== ''
const min = parseOrderLimitCashValue(rawMin)
const max = parseOrderLimitCashValue(rawMax)
if (!hasMin && !hasMax) {
if (hasMinInput && min == null) {
return 'حداقل مبلغ سفارش نامعتبر است'
}
if (hasMaxInput && max == null) {
return 'حداکثر مبلغ سفارش نامعتبر است'
}
if (min == null && max == null) {
return 'با فعال بودن محدودیت مبلغ سفارش، حداقل یکی از حداقل یا حداکثر مبلغ باید وارد شود'
}
if (hasMin && hasMax && Number(min) > Number(max)) {
if (min != null && max != null && min > max) {
return 'حداقل مبلغ نمی‌تواند بیشتر از حداکثر باشد'
}
@@ -22,13 +71,19 @@ export function validateProductOrderLimitForm(form: {
}
export function normalizeProductOrderLimitForm<T extends Record<string, any>>(data: T): T {
data.order_limit_enabled = !!data.order_limit_enabled
if (!data.order_limit_enabled) {
const min = parseOrderLimitCashValue(data.min_order_cash)
const max = parseOrderLimitCashValue(data.max_order_cash)
const enabled = !!data.order_limit_enabled || min != null || max != null
data.order_limit_enabled = enabled
if (!enabled) {
data.min_order_cash = undefined
data.max_order_cash = undefined
} else {
data.min_order_cash = data.min_order_cash != null && data.min_order_cash !== '' ? data.min_order_cash : undefined
data.max_order_cash = data.max_order_cash != null && data.max_order_cash !== '' ? data.max_order_cash : undefined
data.min_order_cash = min
data.max_order_cash = max
}
return data
}