862 lines
31 KiB
TypeScript
862 lines
31 KiB
TypeScript
export type RegistryPresentationColumn = {
|
|
key: "name" | "status" | "version" | "updated_at" | "visibility";
|
|
label: string;
|
|
};
|
|
|
|
export type RegistryPresentation = {
|
|
label: string;
|
|
englishLabel: string;
|
|
pluralLabel: string;
|
|
description: string;
|
|
icon: string;
|
|
accentColor: string;
|
|
navigationTitle: string;
|
|
sectionTitle: string;
|
|
emptyStateTitle: string;
|
|
emptyStateDescription: string;
|
|
createButtonText: string;
|
|
editDialogTitle: string;
|
|
deleteConfirmation: string;
|
|
publishLabel: string;
|
|
archiveLabel: string;
|
|
statusMap: Readonly<Record<string, string>>;
|
|
columns: readonly RegistryPresentationColumn[];
|
|
};
|
|
|
|
const STATUS_LABELS: Readonly<Record<string, string>> = {
|
|
active: "فعال",
|
|
inactive: "غیرفعال",
|
|
draft: "پیشنویس",
|
|
published: "منتشرشده",
|
|
pending: "در انتظار بررسی",
|
|
pending_payment: "در انتظار پرداخت",
|
|
verified: "تأییدشده",
|
|
unverified: "تأییدنشده",
|
|
failed: "ناموفق",
|
|
succeeded: "موفق",
|
|
completed: "تکمیلشده",
|
|
expired: "منقضیشده",
|
|
trialing: "در دوره آزمایشی",
|
|
trial: "آزمایشی",
|
|
suspended: "تعلیقشده",
|
|
canceled: "لغوشده",
|
|
cancelled: "لغوشده",
|
|
archived: "بایگانیشده",
|
|
deprecated: "از رده خارج",
|
|
planned: "برنامهریزیشده",
|
|
concept: "در حال طراحی",
|
|
mature: "پایدار",
|
|
ga: "آماده ارائه",
|
|
beta: "نسخه آزمایشی",
|
|
grace_period: "در مهلت تمدید",
|
|
processing: "در حال پردازش",
|
|
paid: "پرداختشده",
|
|
unpaid: "پرداختنشده",
|
|
refunded: "بازپرداختشده",
|
|
partially_refunded: "بازپرداخت جزئی",
|
|
};
|
|
|
|
const DEFAULT_COLUMNS: readonly RegistryPresentationColumn[] = [
|
|
{ key: "name", label: "نام" },
|
|
{ key: "status", label: "وضعیت" },
|
|
{ key: "version", label: "نسخه" },
|
|
{ key: "updated_at", label: "آخرین تغییر" },
|
|
{ key: "visibility", label: "نمایش" },
|
|
];
|
|
|
|
function definePresentation({
|
|
label,
|
|
englishLabel,
|
|
pluralLabel = label,
|
|
description,
|
|
icon,
|
|
accentColor,
|
|
columns = DEFAULT_COLUMNS,
|
|
}: {
|
|
label: string;
|
|
englishLabel: string;
|
|
pluralLabel?: string;
|
|
description: string;
|
|
icon: string;
|
|
accentColor: string;
|
|
columns?: readonly RegistryPresentationColumn[];
|
|
}): RegistryPresentation {
|
|
return {
|
|
label,
|
|
englishLabel,
|
|
pluralLabel,
|
|
description,
|
|
icon,
|
|
accentColor,
|
|
navigationTitle: pluralLabel,
|
|
sectionTitle: `مدیریت ${pluralLabel}`,
|
|
emptyStateTitle: `هنوز ${pluralLabel} ثبت نشده است`,
|
|
emptyStateDescription: `پس از ثبت اولین مورد، اطلاعات ${pluralLabel} در این بخش نمایش داده میشود.`,
|
|
createButtonText: `افزودن ${label}`,
|
|
editDialogTitle: `ویرایش ${label}`,
|
|
deleteConfirmation: `آیا از حذف این ${label} مطمئن هستید؟`,
|
|
publishLabel: `انتشار ${label}`,
|
|
archiveLabel: `بایگانی ${label}`,
|
|
statusMap: STATUS_LABELS,
|
|
columns,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Universal Commercial Presentation Dictionary.
|
|
* API paths and registry records never provide user-facing copy.
|
|
*/
|
|
export const presentationRegistry = {
|
|
products: definePresentation({
|
|
label: "محصول",
|
|
englishLabel: "Product",
|
|
pluralLabel: "محصولات",
|
|
description: "راهکارهای نرمافزاری قابل ارائه به کسبوکارها",
|
|
icon: "◫",
|
|
accentColor: "bg-sky-50 text-sky-700",
|
|
}),
|
|
bundles: definePresentation({
|
|
label: "بسته خدمات",
|
|
englishLabel: "Service Bundle",
|
|
pluralLabel: "بستههای خدمات",
|
|
description: "ترکیب آماده محصولات و امکانات برای نیازهای مختلف",
|
|
icon: "▦",
|
|
accentColor: "bg-violet-50 text-violet-700",
|
|
}),
|
|
pricing: definePresentation({
|
|
label: "قیمت",
|
|
englishLabel: "Pricing Item",
|
|
pluralLabel: "قیمتگذاری",
|
|
description: "قیمتهای ماهانه، سالانه، آزمایشی و سازمانی",
|
|
icon: "◉",
|
|
accentColor: "bg-emerald-50 text-emerald-700",
|
|
}),
|
|
plans: definePresentation({
|
|
label: "پلن اشتراک",
|
|
englishLabel: "Subscription Plan",
|
|
pluralLabel: "پلنهای اشتراک",
|
|
description: "پلنهای قابل انتخاب برای شروع و توسعه کسبوکار",
|
|
icon: "▤",
|
|
accentColor: "bg-indigo-50 text-indigo-700",
|
|
}),
|
|
capabilities: definePresentation({
|
|
label: "قابلیت",
|
|
englishLabel: "Capability",
|
|
pluralLabel: "قابلیتها",
|
|
description: "امکانات کاربردی قابل ارائه در محصولات و بستهها",
|
|
icon: "✦",
|
|
accentColor: "bg-amber-50 text-amber-700",
|
|
}),
|
|
"business-types": definePresentation({
|
|
label: "نوع کسبوکار",
|
|
englishLabel: "Business Type",
|
|
pluralLabel: "انواع کسبوکار",
|
|
description: "الگوهای کسبوکار برای پیشنهاد دقیقتر محصولات",
|
|
icon: "⌂",
|
|
accentColor: "bg-orange-50 text-orange-700",
|
|
}),
|
|
"recommendation-rules": definePresentation({
|
|
label: "قاعده پیشنهاد",
|
|
englishLabel: "Recommendation Rule",
|
|
pluralLabel: "قواعد پیشنهاد",
|
|
description: "قواعد تجاری برای پیشنهاد راهکار مناسب به هر کسبوکار",
|
|
icon: "◎",
|
|
accentColor: "bg-fuchsia-50 text-fuchsia-700",
|
|
}),
|
|
"assessment-schemas": definePresentation({
|
|
label: "الگوی ارزیابی",
|
|
englishLabel: "Assessment Schema",
|
|
pluralLabel: "الگوهای ارزیابی",
|
|
description: "پرسشهای کسبوکاری برای شناخت نیاز مشتری",
|
|
icon: "☷",
|
|
accentColor: "bg-cyan-50 text-cyan-700",
|
|
}),
|
|
assets: definePresentation({
|
|
label: "دارایی دیجیتال",
|
|
englishLabel: "Digital Asset",
|
|
pluralLabel: "داراییهای دیجیتال",
|
|
description: "داراییهای قابل استفاده در محصولات و تجربه مشتری",
|
|
icon: "◇",
|
|
accentColor: "bg-rose-50 text-rose-700",
|
|
}),
|
|
extensions: definePresentation({
|
|
label: "افزونه",
|
|
englishLabel: "Extension",
|
|
pluralLabel: "افزونهها",
|
|
description: "امکانات تکمیلی قابل افزودن به محصولات",
|
|
icon: "⊞",
|
|
accentColor: "bg-teal-50 text-teal-700",
|
|
}),
|
|
"automation-packs": definePresentation({
|
|
label: "بسته اتوماسیون",
|
|
englishLabel: "Automation Pack",
|
|
pluralLabel: "بستههای اتوماسیون",
|
|
description: "فرایندهای آماده برای کاهش کارهای تکراری",
|
|
icon: "⚙",
|
|
accentColor: "bg-blue-50 text-blue-700",
|
|
}),
|
|
metadata: definePresentation({
|
|
label: "ویژگی تکمیلی",
|
|
englishLabel: "Additional Attribute",
|
|
pluralLabel: "ویژگیهای تکمیلی",
|
|
description: "اطلاعات تکمیلی سازمانیافته برای نمایش و دستهبندی",
|
|
icon: "≡",
|
|
accentColor: "bg-slate-100 text-slate-700",
|
|
}),
|
|
policies: definePresentation({
|
|
label: "قانون تجاری",
|
|
englishLabel: "Commercial Policy",
|
|
pluralLabel: "قوانین تجاری",
|
|
description: "قوانین دسترسی، دامنه، فروش و ارائه خدمات",
|
|
icon: "✓",
|
|
accentColor: "bg-lime-50 text-lime-700",
|
|
}),
|
|
"usage-plans": definePresentation({
|
|
label: "برنامه مصرف",
|
|
englishLabel: "Usage Plan",
|
|
pluralLabel: "برنامههای مصرف",
|
|
description: "سقفها و الگوهای مصرف خدمات",
|
|
icon: "◔",
|
|
accentColor: "bg-purple-50 text-purple-700",
|
|
}),
|
|
subscriptions: definePresentation({
|
|
label: "اشتراک",
|
|
englishLabel: "Subscription",
|
|
pluralLabel: "اشتراکها",
|
|
description: "اشتراکهای فعال، آزمایشی و در انتظار تمدید مشتریان",
|
|
icon: "↻",
|
|
accentColor: "bg-sky-50 text-sky-700",
|
|
}),
|
|
licenses: definePresentation({
|
|
label: "لایسنس",
|
|
englishLabel: "License",
|
|
pluralLabel: "لایسنسها",
|
|
description: "مجوزهای استفاده از محصولات و بستههای خریداریشده",
|
|
icon: "⌘",
|
|
accentColor: "bg-indigo-50 text-indigo-700",
|
|
}),
|
|
entitlements: definePresentation({
|
|
label: "مجوز دسترسی",
|
|
englishLabel: "Entitlement",
|
|
pluralLabel: "مجوزهای دسترسی",
|
|
description: "امکانات و سهمیههای در دسترس هر فضای کاری",
|
|
icon: "◈",
|
|
accentColor: "bg-emerald-50 text-emerald-700",
|
|
}),
|
|
promotions: definePresentation({
|
|
label: "پیشنهاد ویژه",
|
|
englishLabel: "Promotion",
|
|
pluralLabel: "پیشنهادهای ویژه",
|
|
description: "پیشنهادهای فروش زماندار برای مشتریان",
|
|
icon: "★",
|
|
accentColor: "bg-pink-50 text-pink-700",
|
|
}),
|
|
coupons: definePresentation({
|
|
label: "کوپن",
|
|
englishLabel: "Coupon",
|
|
pluralLabel: "کوپنها",
|
|
description: "کوپنهای تخفیف قابل استفاده هنگام خرید",
|
|
icon: "✂",
|
|
accentColor: "bg-rose-50 text-rose-700",
|
|
}),
|
|
currencies: definePresentation({
|
|
label: "واحد پول",
|
|
englishLabel: "Currency",
|
|
pluralLabel: "واحدهای پول",
|
|
description: "واحدهای پول قابل استفاده در قیمتگذاری",
|
|
icon: "¤",
|
|
accentColor: "bg-green-50 text-green-700",
|
|
}),
|
|
taxes: definePresentation({
|
|
label: "گروه مالیاتی",
|
|
englishLabel: "Tax Class",
|
|
pluralLabel: "گروههای مالیاتی",
|
|
description: "قواعد محاسبه مالیات برای محصولات و خدمات",
|
|
icon: "%",
|
|
accentColor: "bg-amber-50 text-amber-700",
|
|
}),
|
|
notifications: definePresentation({
|
|
label: "اعلان تجاری",
|
|
englishLabel: "Commercial Notification",
|
|
pluralLabel: "اعلانهای تجاری",
|
|
description: "پیامهای مرتبط با خرید، اشتراک و تمدید",
|
|
icon: "◌",
|
|
accentColor: "bg-cyan-50 text-cyan-700",
|
|
}),
|
|
announcements: definePresentation({
|
|
label: "اطلاعیه تجاری",
|
|
englishLabel: "Commercial Announcement",
|
|
pluralLabel: "اطلاعیههای تجاری",
|
|
description: "اطلاعیههای قابل نمایش به مشتریان",
|
|
icon: "◍",
|
|
accentColor: "bg-blue-50 text-blue-700",
|
|
}),
|
|
transactions: definePresentation({
|
|
label: "تراکنش",
|
|
englishLabel: "Transaction",
|
|
pluralLabel: "تراکنشها",
|
|
description: "سوابق پرداخت و بازپرداخت مشتریان",
|
|
icon: "⇄",
|
|
accentColor: "bg-teal-50 text-teal-700",
|
|
}),
|
|
invoices: definePresentation({
|
|
label: "صورتحساب",
|
|
englishLabel: "Invoice",
|
|
pluralLabel: "صورتحسابها",
|
|
description: "صورتحسابهای صادرشده برای اشتراک و خدمات",
|
|
icon: "▧",
|
|
accentColor: "bg-orange-50 text-orange-700",
|
|
}),
|
|
"trial-rules": definePresentation({
|
|
label: "قانون دوره آزمایشی",
|
|
englishLabel: "Trial Rule",
|
|
pluralLabel: "قوانین دوره آزمایشی",
|
|
description: "مدت، محدودیت و شرایط استفاده آزمایشی",
|
|
icon: "◷",
|
|
accentColor: "bg-green-50 text-green-700",
|
|
}),
|
|
"subscription-templates": definePresentation({
|
|
label: "الگوی اشتراک",
|
|
englishLabel: "Subscription Template",
|
|
pluralLabel: "الگوهای اشتراک",
|
|
description: "الگوهای آماده برای ایجاد و تمدید اشتراک",
|
|
icon: "▥",
|
|
accentColor: "bg-indigo-50 text-indigo-700",
|
|
}),
|
|
"discount-campaigns": definePresentation({
|
|
label: "کمپین تخفیف",
|
|
englishLabel: "Discount Campaign",
|
|
pluralLabel: "کمپینهای تخفیف",
|
|
description: "کمپینهای زماندار تخفیف و فروش",
|
|
icon: "↓",
|
|
accentColor: "bg-red-50 text-red-700",
|
|
}),
|
|
"regional-pricing": definePresentation({
|
|
label: "قیمت منطقهای",
|
|
englishLabel: "Regional Pricing",
|
|
pluralLabel: "قیمتهای منطقهای",
|
|
description: "تنظیم قیمت متناسب با منطقه و بازار",
|
|
icon: "⌖",
|
|
accentColor: "bg-cyan-50 text-cyan-700",
|
|
}),
|
|
languages: definePresentation({
|
|
label: "زبان",
|
|
englishLabel: "Language",
|
|
pluralLabel: "زبانها",
|
|
description: "زبانهای قابل استفاده در تجربه مشتری",
|
|
icon: "文",
|
|
accentColor: "bg-violet-50 text-violet-700",
|
|
}),
|
|
"marketplace-visibility": definePresentation({
|
|
label: "نحوه نمایش در بازار",
|
|
englishLabel: "Marketplace Visibility",
|
|
pluralLabel: "تنظیمات نمایش در بازار",
|
|
description: "کنترل نمایش محصولات و خدمات در بازار",
|
|
icon: "◐",
|
|
accentColor: "bg-slate-100 text-slate-700",
|
|
}),
|
|
"release-channels": definePresentation({
|
|
label: "کانال انتشار",
|
|
englishLabel: "Release Channel",
|
|
pluralLabel: "کانالهای انتشار",
|
|
description: "مسیرهای عرضه نسخههای پایدار و آزمایشی",
|
|
icon: "➜",
|
|
accentColor: "bg-blue-50 text-blue-700",
|
|
}),
|
|
"support-levels": definePresentation({
|
|
label: "سطح پشتیبانی",
|
|
englishLabel: "Support Level",
|
|
pluralLabel: "سطوح پشتیبانی",
|
|
description: "سطوح پاسخگویی و خدمات پشتیبانی",
|
|
icon: "☏",
|
|
accentColor: "bg-emerald-50 text-emerald-700",
|
|
}),
|
|
"activation-logs": definePresentation({
|
|
label: "فرایند فعالسازی",
|
|
englishLabel: "Activation Process",
|
|
pluralLabel: "فرایندهای فعالسازی",
|
|
description: "مراحل آمادهسازی محصولات برای فضای کاری",
|
|
icon: "▶",
|
|
accentColor: "bg-lime-50 text-lime-700",
|
|
}),
|
|
} satisfies Record<string, RegistryPresentation>;
|
|
|
|
export type PresentationRegistryKind = keyof typeof presentationRegistry;
|
|
|
|
const KIND_ALIASES: Readonly<Record<string, PresentationRegistryKind>> = {
|
|
product: "products",
|
|
bundle: "bundles",
|
|
price: "pricing",
|
|
prices: "pricing",
|
|
plan: "plans",
|
|
capability: "capabilities",
|
|
"business-type": "business-types",
|
|
recommendations: "recommendation-rules",
|
|
recommendation: "recommendation-rules",
|
|
"recommendation-rules": "recommendation-rules",
|
|
assessments: "assessment-schemas",
|
|
"tax-classes": "taxes",
|
|
tax: "taxes",
|
|
notification: "notifications",
|
|
invoice: "invoices",
|
|
transaction: "transactions",
|
|
license: "licenses",
|
|
entitlement: "entitlements",
|
|
subscription: "subscriptions",
|
|
};
|
|
|
|
export function normalizeRegistryKind(kind: string): PresentationRegistryKind | null {
|
|
const normalized = kind.trim().toLowerCase().replace(/_/g, "-");
|
|
if (normalized in presentationRegistry) return normalized as PresentationRegistryKind;
|
|
return KIND_ALIASES[normalized] || null;
|
|
}
|
|
|
|
export function getRegistryPresentation(kind: string): RegistryPresentation {
|
|
const normalized = normalizeRegistryKind(kind);
|
|
if (normalized) return presentationRegistry[normalized];
|
|
return definePresentation({
|
|
label: "بخش تجاری جدید",
|
|
englishLabel: "New Commercial Section",
|
|
pluralLabel: "اطلاعات بخش تجاری جدید",
|
|
description: "اطلاعات این بخش پس از تکمیل عنوان نمایشی قابل مدیریت است.",
|
|
icon: "◈",
|
|
accentColor: "bg-slate-100 text-slate-700",
|
|
});
|
|
}
|
|
|
|
const TOKEN_LABELS: Readonly<Record<string, string>> = {
|
|
restaurant: "رستوران",
|
|
hospitality: "رستوران و پذیرایی",
|
|
food: "غذا",
|
|
beverage: "پذیرایی",
|
|
platform: "پلتفرم",
|
|
starter: "شروع",
|
|
start: "شروع",
|
|
growth: "رشد",
|
|
professional: "حرفهای",
|
|
pro: "حرفهای",
|
|
enterprise: "سازمانی",
|
|
loyalty: "باشگاه مشتریان",
|
|
delivery: "ارسال و پیک",
|
|
booking: "رزرو",
|
|
reservation: "رزرو",
|
|
payment: "پرداخت",
|
|
accounting: "حسابداری",
|
|
website: "وبسایت",
|
|
experience: "وبسایت و تجربه دیجیتال",
|
|
crm: "مدیریت ارتباط با مشتری",
|
|
inventory: "انبارداری",
|
|
pos: "صندوق فروش",
|
|
communication: "ارتباط با مشتریان",
|
|
marketplace: "بازار آنلاین",
|
|
sports: "مجموعه ورزشی",
|
|
"sports-center": "مجموعه ورزشی",
|
|
identity: "مدیریت کاربران",
|
|
operations: "عملیات",
|
|
sales: "فروش",
|
|
marketing: "بازاریابی",
|
|
finance: "مالی",
|
|
customers: "مشتریان",
|
|
online: "حضور آنلاین",
|
|
online_presence: "حضور آنلاین",
|
|
automation: "اتوماسیون",
|
|
ai: "هوش مصنوعی",
|
|
retail: "خردهفروشی",
|
|
service: "خدمات",
|
|
services: "خدمات",
|
|
ecommerce: "فروشگاه آنلاین",
|
|
clinic: "مرکز درمانی",
|
|
healthcare: "سلامت و درمان",
|
|
beauty: "زیبایی و سلامت",
|
|
education: "آموزش",
|
|
monthly: "ماهانه",
|
|
month: "ماهانه",
|
|
yearly: "سالانه",
|
|
annual: "سالانه",
|
|
year: "سالانه",
|
|
trial: "آزمایشی",
|
|
public: "قابل مشاهده برای همه",
|
|
private: "فقط مشتریان مجاز",
|
|
hidden: "پنهان",
|
|
admin: "فقط مدیران",
|
|
route: "داخل سامانه",
|
|
external: "پیوند بیرونی",
|
|
disabled: "غیرفعال",
|
|
deny: "غیرفعال",
|
|
allow: "فعال",
|
|
enterprise_quote: "استعلام قیمت",
|
|
enterprisequote: "استعلام قیمت",
|
|
irr: "ریال",
|
|
rial: "ریال",
|
|
recommended: "پیشنهادشده",
|
|
popular: "محبوب",
|
|
most_popular: "محبوبترین",
|
|
recurring: "اشتراکی",
|
|
flat: "مبلغ ثابت",
|
|
one_time: "پرداخت یکباره",
|
|
usage_based: "بر اساس مصرف",
|
|
per_user: "بهازای هر کاربر",
|
|
free: "رایگان",
|
|
standard: "استاندارد",
|
|
premium: "ویژه",
|
|
unlimited: "نامحدود",
|
|
required: "ضروری",
|
|
optional: "پیشنهادی",
|
|
later: "قابل افزودن در آینده",
|
|
...STATUS_LABELS,
|
|
};
|
|
|
|
export const FIELD_LABELS: Readonly<Record<string, string>> = {
|
|
display_name: "نام",
|
|
name: "نام",
|
|
title: "عنوان",
|
|
description: "توضیح کوتاه",
|
|
status: "وضعیت",
|
|
role: "نوع اشتراک",
|
|
pricing_model: "مدل پرداخت",
|
|
amount_minor: "قیمت",
|
|
currency: "واحد پول",
|
|
interval: "دوره پرداخت",
|
|
trial_days: "مدت دوره آزمایشی",
|
|
duration_days: "مدت دوره آزمایشی",
|
|
grace_period_days: "مهلت تمدید",
|
|
product_codes: "محصولات",
|
|
capability_codes: "قابلیتها",
|
|
required_capabilities: "قابلیتهای موردنیاز",
|
|
feature_refs: "امکانات",
|
|
pricing_refs: "روشهای پرداخت",
|
|
discount_refs: "تخفیفها",
|
|
coupon_code: "کد تخفیف",
|
|
tax_class_code: "گروه مالیاتی",
|
|
trial_eligible: "امکان استفاده آزمایشی",
|
|
visibility: "نحوه نمایش",
|
|
display_order: "اولویت نمایش",
|
|
category: "دستهبندی",
|
|
is_verified: "وضعیت تأیید",
|
|
domain_type: "نوع دامنه",
|
|
current_period_end: "تاریخ تمدید",
|
|
trial_ends_at: "پایان دوره آزمایشی",
|
|
created_at: "تاریخ ایجاد",
|
|
updated_at: "آخرین تغییر",
|
|
published_at: "تاریخ انتشار",
|
|
starts_at: "تاریخ شروع",
|
|
ends_at: "تاریخ پایان",
|
|
version: "نسخه",
|
|
metadata: "اطلاعات تکمیلی",
|
|
attributes: "ویژگیها",
|
|
parameters: "تنظیمات",
|
|
usage_limits: "سقف استفاده",
|
|
feature_limits: "محدودیت امکانات",
|
|
auto_disable: "غیرفعالسازی خودکار",
|
|
expiration_behavior: "رفتار پس از پایان",
|
|
ssl_ready: "وضعیت گواهی امنیتی",
|
|
dns_instructions: "راهنمای اتصال دامنه",
|
|
custom_domain_allowed: "دامنه اختصاصی",
|
|
subdomain_only: "فقط زیردامنه",
|
|
reason: "دلیل پیشنهاد",
|
|
priority: "اولویت",
|
|
sellable: "قابل فروش",
|
|
lifecycle: "مرحله ارائه",
|
|
recommended_products: "محصولات ضروری",
|
|
recommended_bundles: "بستههای پیشنهادی",
|
|
recommended_services: "خدمات پیشنهادی",
|
|
recommended_upgrades: "پیشنهادهای قابل افزودن",
|
|
recommended_automation_packs: "اتوماسیونهای پیشنهادی",
|
|
recommended_extensions: "افزونههای پیشنهادی",
|
|
score: "میزان تطبیق",
|
|
};
|
|
|
|
export const COMMERCIAL_FIELD_GROUPS = [
|
|
{
|
|
title: "اطلاعات پایه",
|
|
keys: ["display_name", "title", "description", "category"],
|
|
},
|
|
{
|
|
title: "اطلاعات تجاری",
|
|
keys: [
|
|
"role",
|
|
"pricing_model",
|
|
"amount_minor",
|
|
"currency",
|
|
"interval",
|
|
"trial_days",
|
|
"duration_days",
|
|
"grace_period_days",
|
|
],
|
|
},
|
|
{
|
|
title: "قیمتگذاری",
|
|
keys: ["pricing_refs", "tax_class_code", "discount_refs", "coupon_code"],
|
|
},
|
|
{
|
|
title: "قابلیتها",
|
|
keys: [
|
|
"product_codes",
|
|
"capability_codes",
|
|
"feature_refs",
|
|
"required_capabilities",
|
|
],
|
|
},
|
|
{
|
|
title: "نمایش",
|
|
keys: ["visibility", "display_order", "trial_eligible"],
|
|
},
|
|
{
|
|
title: "انتشار",
|
|
keys: ["status", "lifecycle", "published_at", "starts_at", "ends_at"],
|
|
},
|
|
{
|
|
title: "اطلاعات تکمیلی",
|
|
keys: ["metadata", "attributes", "parameters", "usage_limits", "feature_limits"],
|
|
},
|
|
] as const;
|
|
|
|
export const FIELD_ENUM_OPTIONS: Readonly<Record<string, readonly string[]>> = {
|
|
status: [
|
|
"draft",
|
|
"active",
|
|
"published",
|
|
"inactive",
|
|
"suspended",
|
|
"archived",
|
|
],
|
|
visibility: ["public", "private", "hidden", "admin"],
|
|
pricing_model: [
|
|
"monthly",
|
|
"yearly",
|
|
"trial",
|
|
"enterprise_quote",
|
|
"one_time",
|
|
"usage_based",
|
|
],
|
|
interval: ["monthly", "yearly"],
|
|
currency: ["IRR"],
|
|
lifecycle: ["concept", "planned", "beta", "ga", "mature", "deprecated"],
|
|
};
|
|
|
|
const REFERENCE_KIND_BY_FIELD: Readonly<Record<string, PresentationRegistryKind>> = {
|
|
product_codes: "products",
|
|
products: "products",
|
|
recommended_products: "products",
|
|
recommended_services: "products",
|
|
bundle_codes: "bundles",
|
|
bundles: "bundles",
|
|
recommended_bundles: "bundles",
|
|
pricing_refs: "pricing",
|
|
pricing_items: "pricing",
|
|
capability_codes: "capabilities",
|
|
capabilities: "capabilities",
|
|
feature_refs: "capabilities",
|
|
required_capabilities: "capabilities",
|
|
recommended_upgrades: "products",
|
|
recommended_automation_packs: "automation-packs",
|
|
recommended_extensions: "extensions",
|
|
};
|
|
|
|
export function registryKindForField(
|
|
field?: string | null
|
|
): PresentationRegistryKind | null {
|
|
if (!field) return null;
|
|
return REFERENCE_KIND_BY_FIELD[field] || null;
|
|
}
|
|
|
|
const TECHNICAL_FIELD =
|
|
/(^id$|_id$|_code$|^code$|^kind$|^slug$|^stable_code$|^payload$|^raw_json$|^source$|^version_code$|^schema_version$|^rule_trace$|^href$|_route$|launch_strategy|created_by|updated_by|is_deleted|clone_of_id)/i;
|
|
|
|
export function isTechnicalField(key: string): boolean {
|
|
return TECHNICAL_FIELD.test(key);
|
|
}
|
|
|
|
export function fieldLabel(key: string): string {
|
|
if (FIELD_LABELS[key]) return FIELD_LABELS[key];
|
|
const translated = key
|
|
.split(/[_\-.]+/)
|
|
.map((token) => TOKEN_LABELS[token.toLowerCase()] || "")
|
|
.filter(Boolean)
|
|
.join(" ");
|
|
return translated || "ویژگی تکمیلی";
|
|
}
|
|
|
|
export function enumLabel(value: unknown): string {
|
|
if (typeof value === "boolean") return value ? "بله" : "خیر";
|
|
if (value == null || value === "") return "ثبت نشده";
|
|
const raw = String(value).trim();
|
|
const normalized = raw.toLowerCase().replace(/[-\s]+/g, "_");
|
|
const translated = TOKEN_LABELS[normalized] || humanizeCommercialCode(raw);
|
|
return translated === "عنوان تجاری ثبتنشده" ? "سایر" : translated;
|
|
}
|
|
|
|
function hasPersian(value?: string | null): boolean {
|
|
return Boolean(value && /[\u0600-\u06ff]/.test(value));
|
|
}
|
|
|
|
function translatedTokens(tokens: string[]): string[] {
|
|
return tokens
|
|
.map((token) => TOKEN_LABELS[token.toLowerCase()] || "")
|
|
.filter(Boolean);
|
|
}
|
|
|
|
type CodeFormatterContext = {
|
|
translated: string[];
|
|
subject: string;
|
|
interval: string;
|
|
};
|
|
|
|
const CODE_FORMATTERS: Readonly<
|
|
Record<string, (context: CodeFormatterContext) => string>
|
|
> = {
|
|
price: ({ translated, interval }) => {
|
|
const business = translated[0] || "کسبوکار";
|
|
const tier = translated.slice(1).join(" ");
|
|
return `پلن ${business}${tier ? ` — نسخه ${tier}` : ""}${interval ? ` (${interval})` : ""}`;
|
|
},
|
|
pricing: ({ translated, interval }) =>
|
|
CODE_FORMATTERS.price({ translated, subject: translated.join(" "), interval }),
|
|
plan: ({ translated, interval }) =>
|
|
CODE_FORMATTERS.price({ translated, subject: translated.join(" "), interval }),
|
|
bundle: ({ translated }) => {
|
|
const business = translated.find((token) => token !== "شروع") || "کسبوکار";
|
|
const tier = translated.includes("شروع") ? "شروع" : translated.slice(1).join(" ");
|
|
return `بسته ${tier || "خدمات"} ${business}`.trim();
|
|
},
|
|
product: ({ subject }) => `سیستم ${subject || "مدیریت کسبوکار"}`,
|
|
capability: ({ subject }) => subject || "قابلیت کاربردی",
|
|
feature: ({ subject }) => subject || "قابلیت کاربردی",
|
|
tax: ({ subject }) => subject || "گروه مالیاتی",
|
|
coupon: ({ subject }) => subject || "تخفیف",
|
|
discount: ({ subject }) => subject || "تخفیف",
|
|
};
|
|
|
|
export function humanizeCommercialCode(code: unknown, explicitName?: string | null): string {
|
|
if (explicitName && hasPersian(explicitName)) return explicitName.trim();
|
|
if (code == null) return "عنوان تجاری ثبتنشده";
|
|
const raw = String(code).trim();
|
|
if (!raw) return "عنوان تجاری ثبتنشده";
|
|
|
|
const normalized = raw.toLowerCase().replace(/_/g, "-");
|
|
if (TOKEN_LABELS[normalized]) return TOKEN_LABELS[normalized];
|
|
|
|
const tokens = raw.toLowerCase().split(/[._:/-]+/).filter(Boolean);
|
|
const kind = tokens[0];
|
|
const body = tokens.slice(1);
|
|
const intervalToken = body.find((token) => /^(monthly|month|yearly|annual|year|trial)$/.test(token));
|
|
const interval = intervalToken ? TOKEN_LABELS[intervalToken] : "";
|
|
const subjectTokens = body.filter((token) => token !== intervalToken);
|
|
const translated = translatedTokens(subjectTokens);
|
|
const subject = translated.join(" ");
|
|
|
|
const formatter = CODE_FORMATTERS[kind];
|
|
if (formatter) return formatter({ translated, subject, interval });
|
|
if (subject) return subject;
|
|
if (hasPersian(raw)) return raw;
|
|
return "عنوان تجاری ثبتنشده";
|
|
}
|
|
|
|
export function commercialEntityName(
|
|
kind: string,
|
|
code: unknown,
|
|
explicitName?: string | null
|
|
): string {
|
|
if (explicitName && hasPersian(explicitName)) return explicitName.trim();
|
|
const normalizedKind = normalizeRegistryKind(kind);
|
|
const raw = code == null ? "" : String(code);
|
|
const entityPrefixes: Partial<Record<PresentationRegistryKind, string>> = {
|
|
products: "product",
|
|
bundles: "bundle",
|
|
pricing: "price",
|
|
plans: "price",
|
|
capabilities: "capability",
|
|
};
|
|
const prefix = normalizedKind ? entityPrefixes[normalizedKind] : null;
|
|
if (prefix) {
|
|
const translated = humanizeCommercialCode(
|
|
raw.startsWith(`${prefix}.`) ? raw : `${prefix}.${raw}`
|
|
);
|
|
const genericFallbacks = [
|
|
"سیستم مدیریت کسبوکار",
|
|
"بسته خدمات کسبوکار",
|
|
"پلن کسبوکار",
|
|
"قابلیت کاربردی",
|
|
];
|
|
if (
|
|
genericFallbacks.includes(translated) &&
|
|
explicitName?.trim() &&
|
|
!looksTechnical(explicitName)
|
|
) {
|
|
return `${getRegistryPresentation(kind).label} ${explicitName.trim()}`;
|
|
}
|
|
return translated;
|
|
}
|
|
return humanizeCommercialCode(raw, explicitName);
|
|
}
|
|
|
|
export function commercialDescription(
|
|
kind: string,
|
|
description?: string | null
|
|
): string {
|
|
if (description && hasPersian(description)) return description.trim();
|
|
return getRegistryPresentation(kind).description;
|
|
}
|
|
|
|
export function looksTechnical(value: string): boolean {
|
|
return (
|
|
/[a-z0-9]+[._:/][a-z0-9_.:/-]+/i.test(value) ||
|
|
/^[a-z][a-z0-9_-]+$/i.test(value) ||
|
|
/\b(payload|json|registry|runtime|frontend|backend|metadata|heuristic|fallback|trace|api)\b/i.test(
|
|
value
|
|
)
|
|
);
|
|
}
|
|
|
|
export function formatMoney(amount?: number | null, currency?: string | null): string {
|
|
if (amount == null) return "استعلام قیمت";
|
|
const unit = !currency || /irr|rial/i.test(currency) ? "ریال" : enumLabel(currency);
|
|
return `${new Intl.NumberFormat("fa-IR").format(amount)} ${unit}`;
|
|
}
|
|
|
|
export function formatDate(value?: string | null): string {
|
|
if (!value) return "ثبت نشده";
|
|
const date = new Date(value);
|
|
return Number.isNaN(date.getTime())
|
|
? "ثبت نشده"
|
|
: new Intl.DateTimeFormat("fa-IR", { dateStyle: "medium" }).format(date);
|
|
}
|
|
|
|
export function friendlyMessage(
|
|
message?: string | null,
|
|
fallback = "در حال حاضر اطلاعاتی برای نمایش نداریم."
|
|
): string {
|
|
if (!message || looksTechnical(message) || /\b(4\d\d|5\d\d)\b/.test(message)) {
|
|
return fallback;
|
|
}
|
|
return message.replace(/\[object Object\]/g, "اطلاعات تکمیلی");
|
|
}
|
|
|
|
export function recommendationReason(reason?: string | null): string {
|
|
if (!reason || looksTechnical(reason)) return "بر اساس پاسخهای شما";
|
|
const normalized = reason.toLowerCase();
|
|
if (/branch|شعب/.test(normalized)) return "چون چند شعبه دارید";
|
|
if (/delivery|ارسال/.test(normalized)) return "چون ارسال کالا دارید";
|
|
if (/online|website|وب/.test(normalized)) return "چون فروش آنلاین دارید";
|
|
if (/pos|in.person|حضوری|فروش/.test(normalized)) return "چون فروش حضوری دارید";
|
|
if (/loyal|باشگاه/.test(normalized)) return "چون باشگاه مشتریان برای شما مهم است";
|
|
if (/booking|reservation|رزرو/.test(normalized)) return "چون به مدیریت رزرو نیاز دارید";
|
|
return reason;
|
|
}
|
|
|
|
export function presentValue(
|
|
value: unknown
|
|
): string | Array<string | Record<string, unknown>> | Record<string, unknown> {
|
|
if (Array.isArray(value)) {
|
|
return value.map((item) => {
|
|
if (item && typeof item === "object") return item as Record<string, unknown>;
|
|
return typeof item === "string" ? humanizeCommercialCode(item) : enumLabel(item);
|
|
});
|
|
}
|
|
if (value && typeof value === "object") return value as Record<string, unknown>;
|
|
if (typeof value === "string") return enumLabel(value);
|
|
if (typeof value === "boolean") return value ? "بله" : "خیر";
|
|
if (typeof value === "number") return new Intl.NumberFormat("fa-IR").format(value);
|
|
return "ثبت نشده";
|
|
}
|