Add support panel UI, search filters, and money thousand separators.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-08 11:20:26 +03:30
co-authored by Cursor
parent ab4d9ef133
commit d0d621502f
13 changed files with 1169 additions and 132 deletions
+56 -7
View File
@@ -5,6 +5,7 @@ import {
MessageOutlined,
CloudServerOutlined,
AppstoreAddOutlined,
CustomerServiceOutlined,
TeamOutlined,
HomeOutlined,
UserOutlined,
@@ -52,12 +53,15 @@ type ExpiringPlugin = {
}
}
type ExpiringSupport = ExpiringPlugin
const router = useRouter()
const loading = ref(true)
const tenants = ref<Tenant[]>([])
const smsTenants = ref<Tenant[]>([])
const expiringServers = ref<Tenant[]>([])
const expiringPlugins = ref<ExpiringPlugin[]>([])
const expiringSupports = ref<ExpiringSupport[]>([])
const hamkarCount = computed(() => tenants.value.filter((t) => t.product === 'hamkar').length)
const khanegiCount = computed(() => tenants.value.filter((t) => t.product === 'khanegi').length)
@@ -96,16 +100,19 @@ const modules = computed(() => [
async function load() {
loading.value = true
try {
const [tenantsRes, lowestRes, expiringRes, pluginExpiringRes] = await Promise.all([
api.get('/tenants'),
api.get('/sms/lowest', { params: { limit: 5 } }),
api.get('/servers/expiring', { params: { limit: 5 } }),
api.get('/plugins/expiring', { params: { limit: 5 } }),
])
const [tenantsRes, lowestRes, expiringRes, pluginExpiringRes, supportExpiringRes] =
await Promise.all([
api.get('/tenants'),
api.get('/sms/lowest', { params: { limit: 5 } }),
api.get('/servers/expiring', { params: { limit: 5 } }),
api.get('/plugins/expiring', { params: { limit: 5 } }),
api.get('/support/expiring', { params: { limit: 5 } }),
])
tenants.value = tenantsRes.data
smsTenants.value = lowestRes.data
expiringServers.value = expiringRes.data
expiringPlugins.value = pluginExpiringRes.data
expiringSupports.value = supportExpiringRes.data
} catch (e: any) {
message.error(e?.response?.data?.message || 'خطا در دریافت آمار')
} finally {
@@ -137,6 +144,14 @@ function openPluginPanel(tenantId?: string) {
router.push({ name: 'plugin-panel' })
}
function openSupportPanel(tenantId?: string) {
if (tenantId) {
router.push({ name: 'support-tenant', params: { tenantId } })
return
}
router.push({ name: 'support-panel' })
}
function smsTone(level?: BalanceLevel) {
switch (level) {
case 'healthy':
@@ -173,7 +188,7 @@ onMounted(load)
<div class="login-kicker">داشبورد کنترل</div>
<h1 class="page-title">سلام، خوش آمدید</h1>
<p class="page-sub">
از اینجا به پنل پیامکی، سرور، افزونه، مدیران و نسخه‌های همکار / خانگی دسترسی دارید.
از اینجا به پنل پیامکی، سرور، افزونه، پشتیبانی، مدیران و نسخه‌های همکار / خانگی دسترسی دارید.
</p>
<a-spin :spinning="loading">
@@ -285,6 +300,40 @@ onMounted(load)
</div>
</div>
<div class="module-card support sms-dashboard-card" @click="openSupportPanel()">
<div>
<div class="icon-wrap">
<CustomerServiceOutlined />
</div>
<h3>پنل پشتیبانی</h3>
<p>پشتیبانی‌هایی که اعتبارشان رو به پایان است (قابل ثبت رایگان)</p>
</div>
<div class="sms-mini-grid">
<button
v-for="item in expiringSupports"
:key="item.id"
type="button"
class="sms-mini-card"
:class="serverTone(item.expiryLevel)"
@click.stop="openSupportPanel(item.tenantId)"
>
<strong>{{ item.tenant?.nameFa }}</strong>
<span>{{ item.name }}</span>
<small>{{ item.expiryLabel || 'نامشخص' }}</small>
</button>
<div v-if="!expiringSupports.length" class="sms-mini-empty">
پشتیبانی نزدیک‌انقضا یا بدون تاریخ ثبت‌شده نیست
</div>
</div>
<div>
<div class="progress"><span /></div>
<div class="module-meta">
<span>{{ expiringSupports.length }} مورد پیگیری</span>
<span>ورود ←</span>
</div>
</div>
</div>
<button
v-for="item in modules"
:key="item.key"