feat(ux/sms/engine): complete Persian UX writing overhaul, Wolog SMS integration and respin support

This commit is contained in:
s.esmaeili
2026-10-10 01:22:48 +03:30
parent c29d0e31b1
commit 0aafd098f8
223 changed files with 14421 additions and 1846 deletions
@@ -32,7 +32,7 @@ const allTabs = [
];
const allMoreItems = [
{ key: 'm-spins', label: 'تاریخچه اسپین', icon: ReloadOutlined, path: '/m/spins', permission: 'spins:read' },
{ key: 'm-spins', label: 'تاریخچه چرخش‌ها', icon: ReloadOutlined, path: '/m/spins', permission: 'spins:read' },
{ key: 'm-sms', label: 'لاگ پیامک', icon: MessageOutlined, path: '/m/sms', permission: 'sms:read' },
{ key: 'm-reports', label: 'گزارش‌ها', icon: LineChartOutlined, path: '/m/reports', permission: 'reports:read' },
{ key: 'm-audit', label: 'لاگ بازرسی', icon: AuditOutlined, path: '/m/audit', permission: 'audit:read' },
+1 -1
View File
@@ -47,7 +47,7 @@ const mainItems: NavItem[] = [
{ key: 'campaigns', label: 'مدیریت کمپین‌ها', icon: PieChartOutlined, path: '/campaigns', permission: 'campaigns:read' },
{ key: 'users', label: 'مدیریت کاربران', icon: UserOutlined, path: '/users', permission: 'users:read' },
{ key: 'winners', label: 'مدیریت برندگان', icon: TrophyOutlined, path: '/winners', permission: 'winners:read' },
{ key: 'spins', label: 'تاریخچه اسپین‌ها', icon: ReloadOutlined, path: '/spins', permission: 'spins:read' },
{ key: 'spins', label: 'تاریخچه چرخش‌ها', icon: ReloadOutlined, path: '/spins', permission: 'spins:read' },
];
const utilityItems: NavItem[] = [
+1 -1
View File
@@ -14,7 +14,7 @@ const titles: Record<string, string> = {
'm-campaign-prizes': 'مدیریت جوایز',
'm-users': 'کاربران',
'm-user-detail': 'جزئیات کاربر',
'm-spins': 'اسپین‌ها',
'm-spins': 'چرخش‌ها',
'm-winners': 'برندگان',
'm-sms': 'لاگ پیامک',
'm-reports': 'گزارش‌ها',
@@ -129,7 +129,7 @@ const columns = [
{ title: 'تاریخ و ساعت پایان', dataIndex: 'endAt', key: 'endAt' },
{ title: 'نوع جوایز', key: 'prizeTypes' },
{ title: 'تعداد جوایز', key: 'prizesCount', align: 'center' as const },
{ title: 'تعداد اسپین‌ها', key: 'spinsCount', align: 'center' as const },
{ title: 'تعداد چرخش‌ها', key: 'spinsCount', align: 'center' as const },
{ title: 'عملیات', key: 'actions', width: 70, align: 'center' as const },
];
@@ -29,7 +29,7 @@ const designs = [
title: 'کلاسیک کازینویی (Classic Casino)',
badge: 'جشنواره‌ها و قرعه‌کشی',
badgeColor: 'gold',
desc: 'طرح سنتی و مجلل با حباب‌های نور طلایی چشمک‌زن، اسلایس‌های قرمز و مشکی و ستاره برنجی',
desc: 'طرح سنتی و مجلل با حباب‌های نور طلایی چشمک‌زن، بخش‌های قرمز و مشکی و ستاره برنجی',
bg: 'radial-gradient(circle, #2d0a0a 0%, #150505 100%)',
accent: '#fbbf24',
border: '#d97706',
@@ -155,7 +155,7 @@ onMounted(loadCampaigns);
<span>{{ formatDate(c.startAt) }} → {{ formatDate(c.endAt) }}</span>
<span>
{{ formatFarsiNumber(c._count?.prizes || 0) }} جایزه ·
{{ formatFarsiNumber(c._count?.spins || 0) }} اسپین
{{ formatFarsiNumber(c._count?.spins || 0) }} چرخش
</span>
</div>
@@ -67,7 +67,7 @@ function initChart() {
},
series: [
{
name: 'اسپین',
name: 'چرخش',
type: 'line',
smooth: true,
symbol: 'circle',
@@ -132,7 +132,7 @@ onUnmounted(() => {
</div>
<div class="m-kpi amber">
<div class="m-kpi__head">
<span>اسپین‌ها</span>
<span>چرخش‌ها</span>
<ReloadOutlined />
</div>
<div class="m-kpi__val">{{ formatFarsiNumber(dashboardData?.stats?.totalSpins || 0) }}</div>
@@ -181,7 +181,7 @@ onUnmounted(() => {
</div>
<div class="m-card">
<div class="m-card__title">روند اسپین روزانه</div>
<div class="m-card__title">روند چرخش روزانه</div>
<div ref="chartRef" class="m-chart" />
</div>
+2 -2
View File
@@ -68,7 +68,7 @@ onMounted(() => {
<div class="m-kpi">
<ThunderboltOutlined class="ico amber" />
<div class="val">{{ formatFarsiNumber(report?.totalSpins || 0) }}</div>
<div class="lbl">کل اسپین</div>
<div class="lbl">کل چرخش‌ها</div>
</div>
<div class="m-kpi">
<TrophyOutlined class="ico emerald" />
@@ -88,7 +88,7 @@ onMounted(() => {
<div class="m-kpi">
<UserOutlined class="ico slate" />
<div class="val">{{ formatFarsiNumber(report?.zeroSpinUsers || 0) }}</div>
<div class="lbl">بدون اسپین</div>
<div class="lbl">بدون چرخش</div>
</div>
<div class="m-kpi wide">
<ThunderboltOutlined class="ico indigo" />
@@ -40,7 +40,7 @@ const moduleLabels: Record<string, string> = {
prizes: 'جوایز',
users: 'کاربران',
chances: 'شانس‌ها',
spins: 'اسپین‌ها',
spins: 'چرخش‌ها',
winners: 'برندگان',
sms: 'پیامک',
audit: 'بازرسی',
@@ -91,7 +91,7 @@ onMounted(() => {
</div>
<a-spin :spinning="loading">
<div v-if="!spins.length && !loading" class="m-empty">اسپینی یافت نشد</div>
<div v-if="!spins.length && !loading" class="m-empty">هیچ چرخشی یافت نشد</div>
<div v-for="s in spins" :key="s.id" class="m-card">
<div class="m-card__top">
<button
@@ -109,7 +109,7 @@ onMounted(() => {
<div class="m-tabs">
<button type="button" :class="{ active: tab === 'chances' }" @click="tab = 'chances'">شانس‌ها</button>
<button type="button" :class="{ active: tab === 'prizes' }" @click="tab = 'prizes'">جوایز</button>
<button type="button" :class="{ active: tab === 'spins' }" @click="tab = 'spins'">اسپین‌ها</button>
<button type="button" :class="{ active: tab === 'spins' }" @click="tab = 'spins'">چرخش‌ها</button>
</div>
<div v-if="tab === 'chances'">
@@ -157,7 +157,7 @@ onMounted(() => {
<div class="m-card__row"><span>IP</span><strong dir="ltr">{{ s.ipAddress || '—' }}</strong></div>
<div class="m-card__row"><span>زمان</span><strong>{{ formatDate(s.createdAt) }}</strong></div>
</div>
<div v-if="!(user?.spins || []).length" class="m-empty">اسپینی نیست</div>
<div v-if="!(user?.spins || []).length" class="m-empty">هیچ چرخشی ثبت نشده است</div>
</div>
</a-spin>
@@ -162,7 +162,7 @@ onMounted(() => {
</span>
</div>
<div class="m-user-card__meta">
{{ formatFarsiNumber(u._count?.spins || 0) }} اسپین ·
{{ formatFarsiNumber(u._count?.spins || 0) }} چرخش ·
{{ formatFarsiNumber(u._count?.userPrizes || u._count?.prizes || 0) }} جایزه ·
عضویت {{ formatDate(u.createdAt) }}
</div>
@@ -542,7 +542,7 @@ onMounted(() => {
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="ترتیب در گردونه (۱ به بعد)" required tooltip="شماره موقعیت این اسلایس روی گردونه">
<a-form-item label="ترتیب در گردونه (۱ به بعد)" required tooltip="شماره موقعیت این بخش روی گردونه">
<a-input-number
v-model:value="formState.sortOrder"
:min="1"
@@ -641,7 +641,7 @@ onMounted(() => {
<!-- بخش ۴: ظاهر و قالب پیامک -->
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="رنگ پس‌زمینه اسلایس (Hex)" required>
<a-form-item label="رنگ پس‌زمینه بخش (Hex)" required>
<div style="display: flex; gap: 8px; align-items: center">
<input
v-model="formState.segmentColor"
@@ -47,7 +47,7 @@ const moduleLabels: Record<string, string> = {
prizes: 'مدیریت جوایز',
users: 'کاربران سامانه',
chances: 'تنظیم دستی شانس‌ها',
spins: 'تاریخچه اسپین‌ها',
spins: 'تاریخچه چرخش‌ها',
winners: 'مدیریت برندگان و کدهای هدیه',
sms: 'لاگ و ارسال پیامک',
audit: 'لاگ بازرسی (Audit)',
+1 -1
View File
@@ -139,7 +139,7 @@ onMounted(() => {
<div class="spin-list-page">
<div class="page-header-title">
<div>
<h1 class="title">تاریخچه چرخش‌ها (Spins)</h1>
<h1 class="title">تاریخچه چرخش‌های گردونه</h1>
<p class="subtitle">گزارش لحظه‌ای تمام تلاش‌ها، برندگان، کدها و آی‌پی‌های ثبت‌شده</p>
</div>
<a-space>
@@ -122,7 +122,7 @@ const spinColumns = [
{ title: 'نتیجه / جایزه', dataIndex: ['prize', 'title'], key: 'prize' },
{ title: 'برد؟', dataIndex: 'isWin', key: 'isWin' },
{ title: 'آی‌پی', dataIndex: 'ipAddress', key: 'ipAddress' },
{ title: 'تاریخ اسپین', dataIndex: 'createdAt', key: 'createdAt' },
{ title: 'زمان چرخش', dataIndex: 'createdAt', key: 'createdAt' },
];
onMounted(() => {
@@ -245,7 +245,7 @@ onMounted(() => {
</a-table>
</a-tab-pane>
<a-tab-pane key="spins" tab="تاریخچه اسپین‌ها">
<a-tab-pane key="spins" tab="تاریخچه چرخش‌ها">
<a-table
:dataSource="user?.spins || []"
:columns="spinColumns"
+1 -1
View File
@@ -141,7 +141,7 @@ const columns = [
{ title: 'نام کاربر', dataIndex: 'name', key: 'name' },
{ title: 'تاریخ ثبت‌نام', dataIndex: 'createdAt', key: 'createdAt' },
{ title: 'آخرین ورود', dataIndex: 'lastLoginAt', key: 'lastLoginAt' },
{ title: 'تعداد اسپین', dataIndex: ['_count', 'spins'], key: 'spinsCount', align: 'center' as const },
{ title: 'تعداد چرخش‌ها', dataIndex: ['_count', 'spins'], key: 'spinsCount', align: 'center' as const },
{ title: 'تعداد جوایز برنده', key: 'prizesCount', align: 'center' as const },
{ title: 'وضعیت حساب', dataIndex: 'status', key: 'status', align: 'center' as const },
{ title: 'عملیات', key: 'actions', width: 70, align: 'center' as const },