Files
boursecu_admin/src/pages/menuPages/smsPanel/index.vue
T

1492 lines
52 KiB
Vue
Raw Normal View History

2026-07-04 14:21:27 +03:30
<template>
<div class="sms-panel-page h-full min-h-0 w-full min-w-0">
<div class="sms-panel-page__scroll h-full min-h-0 overflow-y-auto overflow-x-hidden px-2 pb-8 md:px-4">
<header class="mb-6 md:mb-8">
<h1 class="sms-panel-heading text-xl font-bold md:text-2xl">پنل پیامک</h1>
<p class="sms-panel-subtitle mt-1 text-sm">مدیریت کیف پول، تعرفه و گزارش ارسال پیامک</p>
</header>
<div v-if="pageLoading" class="flex justify-center py-16">
<a-spin size="large" />
</div>
<template v-else-if="overview">
<!-- آمار کلی -->
<section
class="sms-stat-cards mb-6 grid grid-cols-1 gap-2 sm:grid-cols-2 sm:gap-4 xl:grid-cols-4 md:mb-8 md:gap-5"
>
<div
class="sms-stat-card relative overflow-hidden rounded-xl border p-3 shadow-sm transition-shadow hover:shadow-md md:rounded-2xl md:p-6"
:class="balanceCardClass"
>
<div class="sms-stat-inline flex gap-2.5 md:block">
<div
class="sms-stat-icon flex h-9 w-9 shrink-0 items-center justify-center rounded-lg md:mb-4 md:h-11 md:w-11 md:rounded-xl"
:class="balanceIconClass"
>
<Icon icon="vuesax-linear:wallet-3" size="18px" class="md:!text-[22px]" />
</div>
<div class="sms-stat-inline-body sms-balance-stat-body flex min-w-0 flex-1 justify-between gap-2 md:block">
<div class="sms-balance-stat-title flex min-w-0 items-center gap-1 leading-none md:hidden">
<span class="sms-stat-label shrink-0 text-xs font-medium leading-none md:hidden">موجودی کیف پول</span>
<a-tooltip
v-if="overview.wallet.balance <= 0"
trigger="click"
placement="top"
overlay-class-name="sms-balance-info-tooltip"
>
<template #title>{{ balanceAlertHint }}</template>
<button
type="button"
class="sms-balance-info-btn inline-flex shrink-0 items-center justify-center text-[var(--admin-text-secondary)] transition-colors hover:text-[var(--admin-text-muted)]"
aria-label="توضیح هشدار موجودی"
@click.stop
>
<svg class="h-4 w-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<circle cx="8" cy="8" r="6.25" stroke="currentColor" stroke-width="1.1" />
<circle cx="8" cy="5.2" r="0.55" fill="currentColor" />
<path d="M8 7.2v3.3" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" />
</svg>
</button>
</a-tooltip>
<a-tag
v-if="overview.wallet.is_blocked"
color="error"
class="!m-0 shrink-0 !text-[10px] leading-none"
>
ارسال مسدود
</a-tag>
</div>
<p
class="sms-stat-value shrink-0 whitespace-nowrap text-base font-bold leading-none tracking-tight md:hidden"
:class="balanceTextClass"
>
{{ formatNumber(overview.wallet.balance) }}
<span class="text-xs font-normal opacity-70">ریال</span>
</p>
<div class="hidden w-full md:block">
<div class="mb-2 flex items-center justify-between gap-2">
<div class="sms-balance-stat-title-desktop flex items-center gap-1">
<span class="sms-stat-label text-sm font-medium leading-none">موجودی کیف پول</span>
<a-tooltip
v-if="overview.wallet.balance <= 0"
trigger="click"
placement="top"
overlay-class-name="sms-balance-info-tooltip"
>
<template #title>{{ balanceAlertHint }}</template>
<button
type="button"
class="sms-balance-info-btn inline-flex shrink-0 items-center justify-center text-[var(--admin-text-secondary)] transition-colors hover:text-[var(--admin-text-muted)]"
aria-label="توضیح هشدار موجودی"
@click.stop
>
<svg class="h-4 w-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<circle cx="8" cy="8" r="6.25" stroke="currentColor" stroke-width="1.1" />
<circle cx="8" cy="5.2" r="0.55" fill="currentColor" />
<path d="M8 7.2v3.3" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" />
</svg>
</button>
</a-tooltip>
</div>
<a-tag
v-if="overview.wallet.is_blocked"
color="error"
class="!m-0 !text-xs"
>
ارسال مسدود
</a-tag>
</div>
<p
class="sms-stat-value whitespace-nowrap text-3xl font-bold leading-normal tracking-tight"
:class="balanceTextClass"
>
{{ formatNumber(overview.wallet.balance) }}
<span class="text-sm font-normal opacity-70">ریال</span>
</p>
</div>
</div>
</div>
</div>
<div
class="sms-stat-card sms-stat-card--sky rounded-xl border p-3 shadow-sm md:rounded-2xl md:p-6"
>
<div class="sms-stat-inline flex gap-2.5 md:block">
<div
class="sms-stat-icon sms-stat-icon--sky flex h-9 w-9 shrink-0 items-center justify-center rounded-lg md:mb-4 md:h-11 md:w-11 md:rounded-xl"
>
<Icon icon="vuesax-linear:tag" Size="18px" class="md:!text-[22px]" />
</div>
<div class="sms-stat-inline-body flex min-w-0 flex-1 justify-between gap-2 md:block">
<p class="sms-stat-label shrink-0 text-xs font-medium leading-none md:mb-2 md:text-sm md:leading-normal">
قیمت هر پارت
</p>
<p class="sms-stat-value sms-stat-value--sky shrink-0 whitespace-nowrap text-base font-bold leading-none md:text-3xl md:leading-normal">
{{ formatNumber(overview.wallet.price_per_part) }}
<span class="sms-stat-unit text-xs font-normal md:text-sm">ریال</span>
</p>
</div>
</div>
</div>
<div
class="sms-stat-card sms-stat-card--emerald rounded-xl border p-3 shadow-sm md:rounded-2xl md:p-6"
>
<div class="sms-stat-inline flex gap-2.5 md:block">
<div
class="sms-stat-icon sms-stat-icon--emerald flex h-9 w-9 shrink-0 items-center justify-center rounded-lg md:mb-4 md:h-11 md:w-11 md:rounded-xl"
>
<Icon icon="vuesax-linear:sms" Size="18px" class="md:!text-[22px]" />
</div>
<div class="sms-stat-inline-body sms-stat-period-body flex min-w-0 flex-1 items-center md:block">
<p class="sms-stat-label shrink-0 text-xs font-medium leading-none md:mb-2 md:text-sm md:leading-normal">
امروز
</p>
<p class="sms-stat-period-mid min-w-0 flex-1 text-center text-sm leading-none md:hidden">
<span class="sms-stat-value sms-stat-value--emerald font-bold">
{{ formatNumber(overview.stats.today.messages) }}
<span class="sms-stat-unit text-xs font-medium">پیامک</span>
</span>
<span class="sms-stat-meta">
· {{ formatNumber(overview.stats.today.parts) }} پارت
</span>
</p>
<p class="sms-stat-meta shrink-0 whitespace-nowrap text-sm leading-none md:hidden">
{{ formatNumber(overview.stats.today.amount) }} ریال
</p>
<p class="sms-stat-value sms-stat-value--emerald hidden text-xl font-bold md:block">
{{ formatNumber(overview.stats.today.messages) }}
<span class="sms-stat-unit text-sm font-medium">پیامک</span>
</p>
<p class="sms-stat-meta hidden text-sm md:mt-2 md:block">
{{ formatNumber(overview.stats.today.parts) }} پارت ·
{{ formatNumber(overview.stats.today.amount) }} ریال
</p>
</div>
</div>
</div>
<div
class="sms-stat-card sms-stat-card--violet rounded-xl border p-3 shadow-sm md:rounded-2xl md:p-6"
>
<div class="sms-stat-inline flex gap-2.5 md:block">
<div
class="sms-stat-icon sms-stat-icon--violet flex h-9 w-9 shrink-0 items-center justify-center rounded-lg md:mb-4 md:h-11 md:w-11 md:rounded-xl"
>
<Icon icon="vuesax-linear:chart" Size="18px" class="md:!text-[22px]" />
</div>
<div class="sms-stat-inline-body sms-stat-period-body flex min-w-0 flex-1 items-center md:block">
<p class="sms-stat-label shrink-0 text-xs font-medium leading-none md:mb-2 md:text-sm md:leading-normal">
ماه جاری
</p>
<p class="sms-stat-period-mid min-w-0 flex-1 text-center text-sm leading-none md:hidden">
<span class="sms-stat-value sms-stat-value--violet font-bold">
{{ formatNumber(overview.stats.month.messages) }}
<span class="sms-stat-unit text-xs font-medium">پیامک</span>
</span>
<span class="sms-stat-meta">
· {{ formatNumber(overview.stats.month.parts) }} پارت
</span>
</p>
<p class="sms-stat-meta shrink-0 whitespace-nowrap text-sm leading-none md:hidden">
{{ formatNumber(overview.stats.month.amount) }} ریال
</p>
<p class="sms-stat-value sms-stat-value--violet hidden text-xl font-bold md:block">
{{ formatNumber(overview.stats.month.messages) }}
<span class="sms-stat-unit text-sm font-medium">پیامک</span>
</p>
<p class="sms-stat-meta hidden text-sm md:mt-2 md:block">
{{ formatNumber(overview.stats.month.parts) }} پارت ·
{{ formatNumber(overview.stats.month.amount) }} ریال
</p>
</div>
</div>
</div>
</section>
<!-- مدیریت و گزارش -->
<section class="sms-panel-mgmt mb-6 flex flex-col gap-4 md:mb-8 md:gap-6">
<div class="sms-panel-surface rounded-2xl p-4 shadow-sm md:p-6">
<div class="sms-panel-section-head mb-4 flex items-center gap-2 pb-3 md:mb-5 md:pb-4">
<Icon icon="vuesax-linear:setting-2" Size="20px" class="sms-panel-section-icon" />
<h2 class="sms-panel-heading text-base font-semibold">مدیریت کیف پول</h2>
</div>
<div class="flex flex-col gap-4 md:gap-6">
<div class="flex flex-col gap-3 md:gap-4 xl:flex-row xl:items-stretch">
<div
class="sms-panel-subcard sms-panel-subcard--sky flex-1 rounded-xl p-3 shadow-sm md:p-5"
>
<div class="mb-3 flex items-center gap-2 md:mb-4">
<span class="sms-stat-icon sms-stat-icon--sky flex h-8 w-8 items-center justify-center rounded-lg">
<Icon icon="vuesax-linear:tag" Size="18px" />
</span>
<div>
<p class="sms-subcard-title text-sm font-semibold">تعرفه پیامک</p>
<p class="sms-panel-subtitle hidden text-xs md:block">
قیمت هر پارت برای ارسال‌های بعدی
</p>
</div>
</div>
<div class="grid grid-cols-[1fr_auto] items-end gap-2 md:gap-3">
<a-form-item label="قیمت هر پارت (ریال)" class="sms-compact-field !mb-0">
<a-input
v-model:value="pricePerPartField"
inputmode="numeric"
placeholder="قیمت پارت"
size="large"
class="!w-full"
/>
</a-form-item>
<BaseButton
type="primary"
size="large"
class="!w-auto shrink-0 whitespace-nowrap"
:loading="actionLoading"
@click="submitPrice"
>
ذخیره قیمت
</BaseButton>
</div>
</div>
<div
class="sms-panel-subcard sms-panel-subcard--emerald flex-1 rounded-xl p-3 shadow-sm md:p-5"
>
<div class="mb-3 flex items-center gap-2 md:mb-4">
<span
class="sms-stat-icon sms-stat-icon--emerald flex h-8 w-8 items-center justify-center rounded-lg"
>
<Icon icon="vuesax-linear:wallet-add" Size="18px" />
</span>
<div>
<p class="sms-subcard-title text-sm font-semibold">شارژ کیف پول</p>
<p class="sms-panel-subtitle hidden text-xs md:block">
افزایش موجودی برای ارسال پیامک
</p>
</div>
</div>
<div
class="grid grid-cols-[1fr_auto] items-end gap-2 md:grid-cols-2 md:gap-3 xl:grid-cols-[1fr_1.2fr_auto]"
>
<a-form-item label="مبلغ شارژ (ریال)" class="sms-compact-field order-1 !mb-0">
<a-input
v-model:value="chargeAmountField"
inputmode="numeric"
placeholder="مبلغ شارژ"
size="large"
class="!w-full"
/>
</a-form-item>
<a-form-item
label="توضیح (اختیاری)"
class="sms-compact-field order-3 col-span-2 !mb-0 md:order-2 md:col-span-1"
>
<BasicInput v-model:value="chargeNote" place-holder="توضیح شارژ" size="large" />
</a-form-item>
<BaseButton
type="primary"
size="large"
class="order-2 !w-auto shrink-0 whitespace-nowrap md:order-3 md:col-span-2 xl:col-span-1"
:loading="actionLoading"
@click="openChargeConfirm"
>
شارژ کیف پول
</BaseButton>
</div>
</div>
</div>
<div
class="sms-panel-block rounded-xl border p-4 md:p-5"
:class="isBlocked ? 'sms-panel-block--blocked' : 'sms-panel-block--active'"
>
<div class="inline-flex max-w-full flex-col gap-2 self-start">
<div class="sms-block-row flex w-full items-center justify-between gap-4">
<span class="sms-panel-heading font-semibold leading-none">
مسدودسازی ارسال پیامک
</span>
<a-switch
v-model:checked="isBlocked"
class="sms-block-switch !m-0 shrink-0"
:class="isBlocked ? 'sms-block-switch--blocked' : 'sms-block-switch--active'"
:loading="blockLoading"
checked-children="مسدود"
un-checked-children="فعال"
@change="onBlockChange"
/>
</div>
<p class="sms-panel-subtitle text-sm leading-relaxed">
در حالت مسدود، ارسال پیامک متوقف می‌شود.
2026-07-04 14:21:27 +03:30
</p>
</div>
</div>
2026-07-07 20:27:51 +03:30
<div class="sms-panel-subcard sms-panel-subcard--sky rounded-xl p-3 shadow-sm md:p-5">
<div class="mb-3 flex items-center gap-2 md:mb-4">
<span class="sms-stat-icon sms-stat-icon--sky flex h-8 w-8 items-center justify-center rounded-lg">
<Icon icon="vuesax-linear:card" Size="18px" />
</span>
<div>
<p class="sms-subcard-title text-sm font-semibold">اطلاعات واریز شارژ پیامک</p>
<p class="sms-panel-subtitle hidden text-xs md:block">
برای همه مشتری‌ها یکسان است و در پاپ‌آپ «شارژ» هدر نمایش داده می‌شود.
</p>
</div>
</div>
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 md:gap-4">
<a-form-item label="شماره کارت" class="sms-compact-field !mb-0">
<BasicInput v-model:value="depositForm.card_number" place-holder="شماره کارت" />
</a-form-item>
<a-form-item label="شماره شبا" class="sms-compact-field !mb-0">
<BasicInput v-model:value="depositForm.sheba" place-holder="IR..." />
</a-form-item>
<a-form-item label="نام صاحب حساب" class="sms-compact-field !mb-0 md:col-span-2">
<BasicInput v-model:value="depositForm.account_holder" place-holder="نام صاحب حساب" />
</a-form-item>
<a-form-item label="متن راهنما" class="sms-compact-field !mb-0 md:col-span-2">
<a-textarea
v-model:value="depositForm.note"
:auto-size="{ minRows: 2, maxRows: 5 }"
placeholder="متن راهنمای واریز"
/>
</a-form-item>
</div>
<div class="mt-3 flex justify-end md:mt-4">
<BaseButton
type="primary"
size="large"
class="!w-auto shrink-0 whitespace-nowrap"
:loading="depositLoading"
@click="submitDepositInfo"
>
ذخیره اطلاعات واریز
</BaseButton>
</div>
</div>
2026-07-04 14:21:27 +03:30
</div>
</div>
<div class="sms-panel-surface sms-report-card rounded-2xl p-4 shadow-sm md:p-6">
<div class="sms-panel-section-head mb-3 flex items-center gap-2 pb-3 md:mb-4 md:pb-4">
<Icon icon="vuesax-linear:calendar" Size="20px" class="sms-panel-section-icon" />
<h2 class="sms-panel-heading text-base font-semibold">گزارش</h2>
</div>
<a-tabs v-model:activeKey="activeReportTab" class="sms-panel-tabs sms-report-tabs">
<a-tab-pane key="sms" tab="گزارش بازه‌ای پیامک">
<div class="flex flex-col gap-3 md:gap-4">
<div
class="sms-report-filter-row grid grid-cols-[1fr_1fr_auto] items-center gap-2 md:flex md:flex-wrap md:items-end md:gap-4"
>
<a-form-item label="از تاریخ" class="sms-compact-field !mb-0 min-w-0 md:min-w-[160px] md:flex-1">
<BaseDatePicker
v-model:value="reportFrom"
format="YYYY-MM-DD"
class="w-full"
placeholder="از تاریخ"
/>
</a-form-item>
<a-form-item label="تا تاریخ" class="sms-compact-field !mb-0 min-w-0 md:min-w-[160px] md:flex-1">
<BaseDatePicker
v-model:value="reportTo"
format="YYYY-MM-DD"
class="w-full"
placeholder="تا تاریخ"
/>
</a-form-item>
<BaseButton
class="sms-report-action-btn !w-auto shrink-0 whitespace-nowrap px-3 md:!w-auto"
:loading="reportLoading"
@click="loadReport"
>
دریافت گزارش
</BaseButton>
</div>
<div
v-if="report"
class="sms-panel-muted-box space-y-2 rounded-xl p-3 md:space-y-3 md:p-4"
>
<div class="grid grid-cols-3 gap-2 md:gap-3">
<div class="sms-panel-kpi rounded-lg px-2 py-1.5 md:px-3 md:py-2.5">
<span class="sms-panel-subtitle text-[10px] md:text-xs">تعداد پیامک</span>
<p class="sms-panel-heading text-sm font-bold md:text-lg">
{{ formatNumber(report.summary.messages) }}
</p>
</div>
<div class="sms-panel-kpi rounded-lg px-2 py-1.5 md:px-3 md:py-2.5">
<span class="sms-panel-subtitle text-[10px] md:text-xs">تعداد پارت</span>
<p class="sms-panel-heading text-sm font-bold md:text-lg">
{{ formatNumber(report.summary.parts) }}
</p>
</div>
<div class="sms-panel-kpi rounded-lg px-2 py-1.5 md:px-3 md:py-2.5">
<span class="sms-panel-subtitle text-[10px] md:text-xs">مبلغ کل</span>
<p class="sms-panel-heading text-sm font-bold md:text-lg">
{{ formatNumber(report.summary.amount) }}
<span class="sms-panel-subtitle text-[10px] font-normal md:text-sm">ریال</span>
</p>
</div>
</div>
<div v-if="report.by_provider?.length" class="sms-panel-divider border-t pt-2 md:pt-3">
<p class="sms-stat-label mb-1 text-[10px] font-semibold md:mb-2 md:text-xs">
بر اساس ارائه‌دهنده
</p>
<ul class="space-y-1 md:space-y-2">
<li
v-for="row in report.by_provider"
:key="row.provider"
class="sms-panel-kpi flex items-center justify-between gap-2 rounded-lg px-2 py-1.5 text-[11px] md:px-3 md:py-2 md:text-xs"
>
<span class="font-medium">{{ row.provider }}</span>
<span class="sms-panel-subtitle shrink-0">
{{ formatNumber(row.messages) }} پیام · {{ formatNumber(row.amount) }} ریال
</span>
</li>
</ul>
</div>
</div>
</div>
</a-tab-pane>
<a-tab-pane key="charge" tab="گزارش شارژ کیف پول">
<div class="flex flex-col gap-3 md:gap-4">
<div
class="sms-report-filter-row grid grid-cols-[1fr_1fr_auto] items-center gap-2 md:flex md:flex-wrap md:items-end md:gap-4"
>
<a-form-item label="از تاریخ" class="sms-compact-field !mb-0 min-w-0 md:min-w-[160px] md:flex-1">
<BaseDatePicker
v-model:value="chargeReportFrom"
format="YYYY-MM-DD"
class="w-full"
placeholder="از تاریخ"
/>
</a-form-item>
<a-form-item label="تا تاریخ" class="sms-compact-field !mb-0 min-w-0 md:min-w-[160px] md:flex-1">
<BaseDatePicker
v-model:value="chargeReportTo"
format="YYYY-MM-DD"
class="w-full"
placeholder="تا تاریخ"
/>
</a-form-item>
<BaseButton
class="sms-report-action-btn !w-auto shrink-0 whitespace-nowrap px-3 md:!w-auto"
:loading="chargeReportLoading"
@click="loadChargeReport"
>
دریافت گزارش
</BaseButton>
</div>
<p class="sms-panel-subtitle text-xs md:text-sm">
در صورت خالی بودن تاریخ، همه شارژها نمایش داده می‌شوند.
</p>
<div
v-if="chargeReport"
class="sms-panel-muted-box space-y-2 rounded-xl p-3 md:space-y-3 md:p-4"
>
<div class="grid grid-cols-2 gap-2 md:gap-3">
<div class="sms-panel-kpi rounded-lg px-2 py-1.5 md:px-3 md:py-2.5">
<span class="sms-panel-subtitle text-[10px] md:text-xs">تعداد شارژ</span>
<p class="sms-panel-heading text-sm font-bold md:text-lg">
{{ formatNumber(chargeReport.summary.charges) }}
</p>
</div>
<div class="sms-panel-kpi rounded-lg px-2 py-1.5 md:px-3 md:py-2.5">
<span class="sms-panel-subtitle text-[10px] md:text-xs">مبلغ کل شارژ</span>
<p class="sms-stat-value sms-stat-value--emerald text-sm font-bold md:text-lg">
{{ formatNumber(chargeReport.summary.amount) }}
<span class="sms-panel-subtitle text-[10px] font-normal md:text-sm">ریال</span>
</p>
</div>
</div>
<div v-if="chargeReport.items.length" class="sms-panel-divider border-t pt-2 md:pt-3">
<p class="sms-stat-label mb-1 text-[10px] font-semibold md:mb-2 md:text-xs">
لیست شارژها
</p>
<ul class="max-h-64 space-y-1 overflow-y-auto md:max-h-80 md:space-y-2">
<li
v-for="item in chargeReport.items"
:key="item.id"
class="sms-panel-kpi rounded-lg px-2 py-2 text-[11px] md:px-3 md:py-2.5 md:text-xs"
>
<div class="flex items-start justify-between gap-2">
<div class="min-w-0">
<p class="sms-stat-value sms-stat-value--emerald font-semibold">
+{{ formatNumber(item.amount) }} ریال
</p>
<p v-if="item.note" class="sms-panel-subtitle mt-0.5">{{ item.note }}</p>
</div>
<div class="sms-panel-subtitle shrink-0 text-left">
<p>{{ formatReportDate(item.created_at) }}</p>
<p class="mt-0.5">
موجودی:
{{ formatNumber(item.balance_after) }}
</p>
</div>
</div>
</li>
</ul>
</div>
<p v-else class="sms-panel-subtitle text-center text-xs md:text-sm">
شارژی در این بازه یافت نشد.
</p>
</div>
</div>
</a-tab-pane>
</a-tabs>
</div>
</section>
<!-- جداول -->
<section class="sms-panel-surface rounded-2xl p-4 shadow-sm md:p-6">
<a-tabs
v-model:activeKey="activeTab"
class="sms-panel-tabs sms-panel-table-tabs"
:animated="false"
destroy-inactive-tab-pane
>
<a-tab-pane key="logs" tab="لاگ ارسال">
<div
class="sms-panel-muted-box sms-table-date-filter mb-4 grid grid-cols-2 items-end gap-2 rounded-xl p-3 md:mb-5 md:flex md:flex-wrap md:gap-4 md:p-4"
>
<a-form-item label="از تاریخ" class="sms-compact-field !mb-0 min-w-0 md:min-w-[160px] md:flex-1">
<BaseDatePicker
v-model:value="tableFrom"
format="YYYY-MM-DD"
class="w-full"
placeholder="انتخاب تاریخ"
/>
</a-form-item>
<a-form-item label="تا تاریخ" class="sms-compact-field !mb-0 min-w-0 md:min-w-[160px] md:flex-1">
<BaseDatePicker
v-model:value="tableTo"
format="YYYY-MM-DD"
class="w-full"
placeholder="انتخاب تاریخ"
/>
</a-form-item>
</div>
<SmsPanelLogsTable ref="logsTableRef" :from-date="tableFrom" :to-date="tableTo" />
</a-tab-pane>
<a-tab-pane key="ledger" tab="دفترچه کیف پول">
<div
class="sms-panel-muted-box sms-table-date-filter mb-4 grid grid-cols-2 items-end gap-2 rounded-xl p-3 md:mb-5 md:flex md:flex-wrap md:gap-4 md:p-4"
>
<a-form-item label="از تاریخ" class="sms-compact-field !mb-0 min-w-0 md:min-w-[160px] md:flex-1">
<BaseDatePicker
v-model:value="tableFrom"
format="YYYY-MM-DD"
class="w-full"
placeholder="انتخاب تاریخ"
/>
</a-form-item>
<a-form-item label="تا تاریخ" class="sms-compact-field !mb-0 min-w-0 md:min-w-[160px] md:flex-1">
<BaseDatePicker
v-model:value="tableTo"
format="YYYY-MM-DD"
class="w-full"
placeholder="انتخاب تاریخ"
/>
</a-form-item>
</div>
<SmsPanelLedgerTable ref="ledgerTableRef" :from-date="tableFrom" :to-date="tableTo" />
</a-tab-pane>
</a-tabs>
</section>
</template>
</div>
<BasicModal
:visible="chargeModalVisible"
title="تأیید شارژ کیف پول"
width="380px"
ok-text="شارژ"
:loading="actionLoading"
@ok="submitCharge"
@close="chargeModalVisible = false"
>
<p class="sms-panel-subtitle p-3 text-sm">
مبلغ
<strong class="sms-panel-heading">{{ formatNumber(Number(chargeAmount) || 0) }}</strong>
ریال به کیف پول پیامک اضافه شود؟
</p>
</BasicModal>
</div>
</template>
<script setup lang="ts">
import BasicInput from '@/components/BasicInput.vue'
import BasicModal from '@/components/BasicModal.vue'
import BaseDatePicker from '@core/components/BaseDatePicker'
import BaseButton from '@core/components/BaseButton'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import SmsPanelLogsTable from './components/SmsPanelLogsTable.vue'
import SmsPanelLedgerTable from './components/SmsPanelLedgerTable.vue'
import {
chargeSmsWallet,
getSmsPanelChargeReport,
getSmsPanelOverview,
getSmsPanelReport,
updateSmsBlock,
2026-07-07 20:27:51 +03:30
updateSmsDepositInfo,
2026-07-04 14:21:27 +03:30
updateSmsPrice,
type SmsPanelChargeReport,
type SmsPanelOverview,
type SmsPanelReport
} from '@/api/smsPanel'
2026-07-07 20:27:51 +03:30
import { useSmsWalletStore } from '@/store/smsWalletStore'
2026-07-04 14:21:27 +03:30
import { toJalaliNoTime } from '@core/utils/useDataTime'
import { message } from 'ant-design-vue'
2026-07-07 20:27:51 +03:30
const smsWalletStore = useSmsWalletStore()
2026-07-04 14:21:27 +03:30
const pageLoading = ref(true)
const actionLoading = ref(false)
const blockLoading = ref(false)
const isBlocked = ref(false)
const reportLoading = ref(false)
const chargeReportLoading = ref(false)
const overview = ref<SmsPanelOverview | null>(null)
const report = ref<SmsPanelReport | null>(null)
const chargeReport = ref<SmsPanelChargeReport | null>(null)
const chargeAmount = ref<number | null>(null)
const chargeNote = ref('')
const pricePerPart = ref<number | null>(null)
const chargeModalVisible = ref(false)
2026-07-07 20:27:51 +03:30
const depositLoading = ref(false)
const depositForm = reactive({
card_number: '',
sheba: '',
account_holder: '',
note: ''
})
2026-07-04 14:21:27 +03:30
const reportFrom = ref('')
const reportTo = ref('')
const chargeReportFrom = ref('')
const chargeReportTo = ref('')
const tableFrom = ref('')
const tableTo = ref('')
const activeReportTab = ref('sms')
const activeTab = ref('logs')
const logsTableRef = ref<InstanceType<typeof SmsPanelLogsTable> | null>(null)
const ledgerTableRef = ref<InstanceType<typeof SmsPanelLedgerTable> | null>(null)
const balanceCardClass = computed(() => {
if (!overview.value) return 'sms-stat-card--neutral'
if (overview.value.wallet.balance < 0) return 'sms-stat-card--danger'
if (overview.value.wallet.balance <= 0) return 'sms-stat-card--warn'
return 'sms-stat-card--ok'
})
const balanceIconClass = computed(() => {
if (!overview.value) return 'sms-stat-icon--neutral'
if (overview.value.wallet.balance < 0) return 'sms-stat-icon--danger'
if (overview.value.wallet.balance <= 0) return 'sms-stat-icon--warn'
return 'sms-stat-icon--ok'
})
const balanceTextClass = computed(() => {
if (!overview.value) return 'sms-stat-value--neutral'
if (overview.value.wallet.balance < 0) return 'sms-stat-value--danger'
if (overview.value.wallet.balance <= 0) return 'sms-stat-value--warn'
return 'sms-stat-value--ok'
})
const balanceAlertHint = computed(() => {
const sent = overview.value?.wallet.alert_days_sent ?? 0
return `هشدار روزانه حداکثر ۳ روز (${sent}/۳)`
})
function formatNumber(value: number) {
return Number(value || 0).toLocaleString('fa-IR')
}
function formatReportDate(value: string | null | undefined) {
if (!value) return '—'
return toJalaliNoTime(value)
}
function formatAmountDisplay(value: number | null) {
if (value == null || !Number.isFinite(value)) return ''
return String(Math.trunc(value)).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
function parseAmountDisplay(value: string) {
const digits = value.replace(/[^\d]/g, '')
return digits === '' ? null : Number(digits)
}
function useAmountField(source: Ref<number | null>) {
return computed({
get: () => formatAmountDisplay(source.value),
set: (val: string) => {
source.value = parseAmountDisplay(val)
}
})
}
const chargeAmountField = useAmountField(chargeAmount)
const pricePerPartField = useAmountField(pricePerPart)
async function loadOverview() {
pageLoading.value = true
try {
overview.value = await getSmsPanelOverview()
pricePerPart.value = overview.value.wallet.price_per_part || null
isBlocked.value = overview.value.wallet.is_blocked
2026-07-07 20:27:51 +03:30
if (overview.value.deposit) {
depositForm.card_number = overview.value.deposit.card_number ?? ''
depositForm.sheba = overview.value.deposit.sheba ?? ''
depositForm.account_holder = overview.value.deposit.account_holder ?? ''
depositForm.note = overview.value.deposit.note ?? ''
}
2026-07-04 14:21:27 +03:30
} catch {
message.error('خطا در بارگذاری پنل پیامک')
} finally {
pageLoading.value = false
}
}
function openChargeConfirm() {
const amount = Number(chargeAmount.value ?? 0)
if (!amount || amount <= 0) {
message.warning('مبلغ شارژ را وارد کنید')
return
}
chargeModalVisible.value = true
}
async function submitCharge() {
const amount = Number(chargeAmount.value ?? 0)
if (!amount || amount <= 0) return
actionLoading.value = true
try {
const res = await chargeSmsWallet({
amount,
note: chargeNote.value.trim() || undefined
})
message.success(res.message || 'شارژ انجام شد')
chargeModalVisible.value = false
chargeAmount.value = null
chargeNote.value = ''
await loadOverview()
ledgerTableRef.value?.fetch()
if (chargeReport.value) {
await loadChargeReport()
}
} catch (e: any) {
message.error(e?.response?.data?.message || 'خطا در شارژ')
} finally {
actionLoading.value = false
}
}
2026-07-07 20:27:51 +03:30
async function submitDepositInfo() {
depositLoading.value = true
try {
const res = await updateSmsDepositInfo({
card_number: depositForm.card_number.trim(),
sheba: depositForm.sheba.trim(),
account_holder: depositForm.account_holder.trim(),
note: depositForm.note.trim()
})
message.success(res.message || 'اطلاعات واریز ذخیره شد')
if (res.deposit) {
depositForm.card_number = res.deposit.card_number ?? ''
depositForm.sheba = res.deposit.sheba ?? ''
depositForm.account_holder = res.deposit.account_holder ?? ''
depositForm.note = res.deposit.note ?? ''
}
await smsWalletStore.refresh()
} catch (e: any) {
message.error(e?.response?.data?.message || 'خطا در ذخیره اطلاعات واریز')
} finally {
depositLoading.value = false
}
}
2026-07-04 14:21:27 +03:30
async function submitPrice() {
const price = Number(pricePerPart.value ?? 0)
if (Number.isNaN(price) || price < 0) {
message.warning('قیمت معتبر وارد کنید')
return
}
actionLoading.value = true
try {
const res = await updateSmsPrice(price)
message.success(res.message || 'قیمت ذخیره شد')
await loadOverview()
} catch (e: any) {
message.error(e?.response?.data?.message || 'خطا در ذخیره قیمت')
} finally {
actionLoading.value = false
}
}
async function onBlockChange(checked: boolean) {
blockLoading.value = true
try {
const res = await updateSmsBlock(checked)
message.success(res.message)
isBlocked.value = res.is_blocked
await loadOverview()
} catch (e: any) {
isBlocked.value = overview.value?.wallet.is_blocked ?? false
message.error(e?.response?.data?.message || 'خطا در تغییر وضعیت')
} finally {
blockLoading.value = false
}
}
async function loadReport() {
reportLoading.value = true
try {
report.value = await getSmsPanelReport({
from_date: reportFrom.value || undefined,
to_date: reportTo.value || undefined
})
} catch {
message.error('خطا در دریافت گزارش')
} finally {
reportLoading.value = false
}
}
async function loadChargeReport() {
chargeReportLoading.value = true
try {
chargeReport.value = await getSmsPanelChargeReport({
from_date: chargeReportFrom.value || undefined,
to_date: chargeReportTo.value || undefined
})
} catch {
message.error('خطا در دریافت گزارش شارژ')
} finally {
chargeReportLoading.value = false
}
}
onMounted(() => {
loadOverview()
})
</script>
<style scoped>
.sms-panel-page__scroll {
-webkit-overflow-scrolling: touch;
}
.sms-panel-heading {
color: var(--admin-text);
}
.sms-panel-subtitle {
color: var(--admin-text-secondary);
}
.sms-panel-section-icon {
color: var(--admin-text-muted);
}
.sms-panel-section-head {
border-bottom: 1px solid var(--admin-border);
}
.sms-panel-surface {
border: 1px solid var(--admin-border);
background: var(--admin-bg-elevated);
color: var(--admin-text);
}
.sms-panel-muted-box {
border: 1px solid var(--admin-border);
background: var(--admin-bg-muted);
}
.sms-panel-kpi {
background: var(--admin-bg-elevated);
color: var(--admin-text);
}
.sms-panel-divider {
border-color: var(--admin-border);
}
.sms-stat-label {
color: var(--admin-text-secondary);
}
.sms-stat-meta {
color: var(--admin-text-muted);
}
.sms-stat-unit {
color: var(--admin-text-secondary);
}
.sms-stat-card--neutral {
border-color: var(--admin-border);
background: var(--admin-bg-elevated);
}
.sms-stat-card--ok {
border-color: rgba(16, 185, 129, 0.28);
background: linear-gradient(to bottom right, rgba(16, 185, 129, 0.1), var(--admin-bg-elevated));
}
.sms-stat-card--warn {
border-color: rgba(245, 158, 11, 0.32);
background: linear-gradient(to bottom right, rgba(245, 158, 11, 0.12), var(--admin-bg-elevated));
}
.sms-stat-card--danger {
border-color: rgba(239, 68, 68, 0.32);
background: linear-gradient(to bottom right, rgba(239, 68, 68, 0.12), var(--admin-bg-elevated));
}
.sms-stat-card--sky {
border-color: rgba(14, 165, 233, 0.28);
background: linear-gradient(to bottom right, rgba(14, 165, 233, 0.1), var(--admin-bg-elevated));
}
.sms-stat-card--emerald {
border-color: rgba(16, 185, 129, 0.28);
background: linear-gradient(to bottom right, rgba(16, 185, 129, 0.1), var(--admin-bg-elevated));
}
.sms-stat-card--violet {
border-color: rgba(139, 92, 246, 0.28);
background: linear-gradient(to bottom right, rgba(139, 92, 246, 0.1), var(--admin-bg-elevated));
}
.sms-stat-icon {
background: var(--admin-bg-muted);
color: var(--admin-text-muted);
}
.sms-stat-icon--neutral {
background: var(--admin-bg-muted);
color: var(--admin-text-muted);
}
.sms-stat-icon--ok,
.sms-stat-icon--emerald {
background: rgba(16, 185, 129, 0.14);
color: #059669;
}
.sms-stat-icon--warn {
background: rgba(245, 158, 11, 0.16);
color: #d97706;
}
.sms-stat-icon--danger {
background: rgba(239, 68, 68, 0.14);
color: #dc2626;
}
.sms-stat-icon--sky {
background: rgba(14, 165, 233, 0.14);
color: #0284c7;
}
.sms-stat-icon--violet {
background: rgba(139, 92, 246, 0.14);
color: #7c3aed;
}
.sms-stat-value--neutral {
color: var(--admin-text);
}
.sms-stat-value--ok {
color: #047857;
}
.sms-stat-value--warn {
color: #b45309;
}
.sms-stat-value--danger {
color: #b91c1c;
}
.sms-stat-value--sky {
color: #0c4a6e;
}
.sms-stat-value--emerald {
color: #047857;
}
.sms-stat-value--violet {
color: #5b21b6;
}
.sms-panel-subcard {
border: 1px solid var(--admin-border);
background: var(--admin-bg-elevated);
}
.sms-panel-subcard--sky {
border-color: rgba(14, 165, 233, 0.24);
background: linear-gradient(to bottom right, rgba(14, 165, 233, 0.08), var(--admin-bg-elevated));
}
.sms-panel-subcard--emerald {
border-color: rgba(16, 185, 129, 0.24);
background: linear-gradient(to bottom right, rgba(16, 185, 129, 0.08), var(--admin-bg-elevated));
}
.sms-subcard-title {
color: var(--admin-text);
}
.sms-panel-block--active {
border-color: rgba(16, 185, 129, 0.28);
background: rgba(16, 185, 129, 0.08);
}
.sms-panel-block--blocked {
border-color: rgba(239, 68, 68, 0.28);
background: rgba(239, 68, 68, 0.1);
}
.sms-panel-page :deep(.ant-btn-primary:not(:disabled)) {
background-color: var(--admin-gold-light);
border-color: var(--admin-gold-light);
}
.sms-panel-page :deep(.ant-btn-primary:not(:disabled):hover),
.sms-panel-page :deep(.ant-btn-primary:not(:disabled):focus) {
background-color: var(--admin-gold-hover);
border-color: var(--admin-gold-hover);
}
.sms-panel-page :deep(.ant-tabs-ink-bar) {
background: var(--admin-gold-light);
}
.sms-panel-page :deep(.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn) {
color: var(--admin-gold-light);
}
.sms-panel-page :deep(.ant-tabs-tab .ant-tabs-tab-btn) {
color: var(--admin-text-muted);
}
.sms-panel-page :deep(.ant-input-lg),
.sms-panel-page :deep(.ant-picker) {
border-radius: 0.5rem;
}
.sms-panel-page :deep(.ant-form-item-label > label) {
font-weight: 500;
color: var(--admin-text-muted);
}
.sms-balance-info-btn {
width: 1.125rem;
height: 1.125rem;
margin: 0;
padding: 0;
border: none;
background: transparent;
line-height: 0;
vertical-align: middle;
cursor: pointer;
}
.sms-balance-stat-title-desktop {
display: flex;
align-items: center;
gap: 0.25rem;
line-height: 1;
}
.sms-balance-stat-title-desktop :deep(.ant-tooltip) {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
}
.sms-panel-tabs :deep(.ant-tabs-nav) {
margin-bottom: 1.25rem;
}
.sms-panel-tabs :deep(.ant-tabs-tab) {
padding: 0.6rem 1rem;
font-weight: 500;
}
.sms-panel-table-tabs :deep(.ant-tabs-content-holder),
.sms-panel-table-tabs :deep(.ant-tabs-content),
.sms-panel-table-tabs :deep(.ant-tabs-tabpane),
.sms-panel-table-tabs :deep(.sms-panel-table),
.sms-panel-table-tabs :deep(.hr-tamin-base-table),
.sms-panel-table-tabs :deep(.scrollable-container) {
width: 100%;
max-width: 100%;
min-width: 0;
}
.sms-report-tabs :deep(.ant-tabs-nav) {
margin-bottom: 1rem;
}
@media (min-width: 768px) {
.sms-report-tabs :deep(.ant-tabs-nav) {
margin-bottom: 1.25rem;
}
}
.sms-panel-page :deep(.sms-report-card .sms-compact-field .ant-picker) {
height: 2rem;
padding-top: 0;
padding-bottom: 0;
font-size: 0.875rem;
}
.sms-panel-page :deep(.sms-report-card .sms-report-action-btn.ant-btn) {
height: 2rem;
padding-top: 0;
padding-bottom: 0;
font-size: 0.8125rem;
line-height: 1.25rem;
}
.sms-panel-page :deep(.sms-report-card .sms-report-filter-row) {
align-items: center;
}
@media (min-width: 768px) {
.sms-panel-page :deep(.sms-report-card .sms-report-filter-row) {
align-items: flex-end;
}
.sms-panel-page :deep(.sms-report-card .sms-compact-field.ant-form-item) {
margin-bottom: 0;
}
}
.sms-panel-page :deep(.sms-block-switch--active:not(.ant-switch-checked)) {
background: #22c55e;
}
.sms-panel-page :deep(.sms-block-switch--active:not(.ant-switch-checked):hover:not(.ant-switch-disabled)) {
background: #16a34a;
}
.sms-panel-page :deep(.sms-block-switch--blocked.ant-switch-checked) {
background: #ef4444;
}
.sms-panel-page :deep(.sms-block-switch--blocked.ant-switch-checked:hover:not(.ant-switch-disabled)) {
background: #dc2626;
}
.sms-panel-page :deep(.sms-block-row .ant-switch) {
margin-top: 0;
margin-bottom: 0;
vertical-align: middle;
}
.sms-panel-page :deep(.sms-block-row .ant-switch-inner) {
display: flex;
align-items: center;
}
@media (max-width: 767px) {
.sms-stat-inline {
align-items: center;
}
.sms-stat-inline-body {
display: flex;
align-items: center;
height: 2.25rem;
min-height: 2.25rem;
}
.sms-stat-inline-body > p {
margin: 0;
}
.sms-balance-stat-body > p {
margin: 0;
}
.sms-balance-stat-title {
display: flex;
align-items: center;
gap: 0.25rem;
line-height: 1;
min-height: 2.25rem;
margin: 0;
}
.sms-balance-stat-title :deep(.ant-tooltip) {
display: inline-flex;
align-items: center;
line-height: 0;
}
.sms-balance-stat-body :deep(.ant-tooltip) {
display: inline-flex;
align-items: center;
line-height: 0;
}
.sms-stat-period-body {
gap: 0.375rem;
}
.sms-stat-period-mid {
padding-inline: 0.25rem;
}
.sms-panel-page :deep(.sms-compact-field .ant-form-item-label) {
display: none;
}
.sms-panel-page :deep(.sms-compact-field .ant-input-lg),
.sms-panel-page :deep(.sms-compact-field .hr-tamin-input .ant-input-lg) {
padding-top: 0.375rem;
padding-bottom: 0.375rem;
font-size: 0.875rem;
}
.sms-panel-page :deep(.sms-panel-table .hr-tamin-base-table .flex.items-center.justify-between.mb-2) {
justify-content: flex-start;
}
.sms-panel-page :deep(.sms-panel-table .hr-tamin-base-table .flex.items-center.justify-between.mb-2 > .w-full) {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
gap: 0.25rem;
width: 100%;
}
.sms-panel-page :deep(.sms-panel-table .hr-tamin-base-table .flex.items-center.justify-between.mb-2 > .w-full > .flex.flex-col) {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 0.25rem;
}
.sms-panel-page :deep(.sms-panel-table .hr-tamin-base-table .flex.items-center.justify-between.mb-2 > .w-full > .flex.justify-between.items-center.gap-2) {
flex: 1;
min-width: 0;
}
.sms-panel-page :deep(.sms-panel-table .hr-tamin-base-table .flex.items-center.justify-between.mb-2 > .w-full > .flex.justify-between.items-center.gap-2 > .flex.md\:hidden) {
display: none;
}
.sms-panel-page :deep(.sms-panel-table .hr-tamin-base-table .flex.items-center.justify-between.mb-2 > .w-full > .flex.flex-col > .block.md\:hidden) {
display: none;
}
.sms-panel-page :deep(.sms-panel-table .hr-tamin-base-table .flex.items-center.justify-between.mb-2 > .hidden.md\:block) {
display: none !important;
}
.sms-panel-page :deep(.sms-panel-table .ant-affix) {
width: 100% !important;
}
.sms-panel-page :deep(.sms-panel-table .ant-affix > div) {
width: 100% !important;
}
.sms-panel-page :deep(.sms-panel-table-tabs .ant-tabs-content) {
margin-right: 0 !important;
margin-left: 0 !important;
}
.sms-panel-page :deep(.block.md\:hidden .base-table-mobile-card:not(:has(.base-table-card-header))) {
padding: 0.75rem;
}
.sms-panel-page :deep(.block.md\:hidden .space-y-3) {
gap: 0.625rem;
}
.sms-panel-page :deep(.block.md\:hidden .base-table-mobile-card) {
padding: 0;
border-radius: 0.875rem;
border-color: var(--admin-border);
box-shadow: 0 1px 3px var(--admin-shadow);
}
.sms-panel-page :deep(.block.md\:hidden .base-table-mobile-card:hover) {
box-shadow: 0 2px 8px var(--admin-shadow);
transform: none;
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-header) {
margin: 0;
padding: 0.625rem 0.875rem;
border: none;
border-bottom: 1px solid var(--admin-border);
border-radius: 0;
background: var(--admin-bg-muted);
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-row-badge) {
height: 1.625rem;
width: 1.625rem;
background: var(--admin-gold-light);
color: var(--admin-menu-selected-text);
font-size: 0.6875rem;
font-weight: 700;
box-shadow: 0 1px 2px rgb(212 175 55 / 0.35);
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-header-value) {
font-weight: 600;
color: var(--admin-text-muted);
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-grid-2) {
margin: 0;
padding: 0.75rem 0.875rem;
gap: 0.5rem 0.875rem;
background: var(--admin-bg-elevated);
border-bottom: 1px solid var(--admin-border);
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-grid-2 > div) {
min-height: 1.125rem;
padding: 0.25rem 0;
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-grid-2 .base-table-card-grid-label) {
font-weight: 500;
font-size: 0.6875rem;
color: var(--admin-text-secondary);
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-grid-2 .base-table-card-grid-value) {
font-weight: 600;
font-size: 0.75rem;
color: var(--admin-text);
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-stacked) {
margin: 0;
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-stacked-box) {
margin: 0;
padding: 0.75rem 0.875rem 0.875rem;
border: none;
border-radius: 0;
background: var(--admin-bg-muted);
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-stacked-label) {
margin: 0 0 0.5rem;
padding-bottom: 0.375rem;
border-bottom: 1px dashed var(--admin-border);
text-align: center;
font-size: 0.6875rem;
font-weight: 600;
color: var(--admin-text-secondary);
letter-spacing: 0.01em;
}
.sms-panel-page :deep(.block.md\:hidden .base-table-card-stacked-value) {
margin-top: 0 !important;
font-size: 0.8125rem;
line-height: 1.55;
color: var(--admin-text-muted);
}
.sms-panel-page :deep(.block.md\:hidden .space-y-3 > div .group\/row.mb-2) {
margin-bottom: 0.125rem;
}
.sms-panel-page :deep(.block.md\:hidden .space-y-3 > div .group\/row .flex) {
align-items: center;
gap: 0.5rem;
}
.sms-panel-page :deep(.block.md\:hidden .space-y-3 > div .w-1\/3) {
width: 30%;
font-size: 0.65rem;
line-height: 1.2;
}
.sms-panel-page :deep(.block.md\:hidden .space-y-3 > div .text-sm) {
font-size: 0.75rem;
line-height: 1.3;
}
}
</style>
<style lang="less">
html.is-dark {
.sms-panel-page {
.sms-stat-value--ok,
.sms-stat-value--emerald {
color: #6ee7b7;
}
.sms-stat-value--warn {
color: #fcd34d;
}
.sms-stat-value--danger {
color: #fda4af;
}
.sms-stat-value--sky {
color: #7dd3fc;
}
.sms-stat-value--violet {
color: #c4b5fd;
}
.sms-stat-icon--ok,
.sms-stat-icon--emerald {
color: #6ee7b7;
}
.sms-stat-icon--warn {
color: #fcd34d;
}
.sms-stat-icon--danger {
color: #fda4af;
}
.sms-stat-icon--sky {
color: #7dd3fc;
}
.sms-stat-icon--violet {
color: #c4b5fd;
}
}
}
</style>