From ab4d9ef13337776995d91cc4b96ace6f2fab8231 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Sat, 8 Aug 2026 10:22:35 +0330 Subject: [PATCH] Switch SuperAdmin UI to IRANSans and fix mobile overflow. Use iranSans woff2 globally and harden SMS/layout grids so page heads and stats stay inside the viewport on small screens. Co-authored-by: Cursor --- src/layouts/AppLayout.vue | 8 +- src/pages/PluginPanelPage.vue | 2 +- src/pages/ServerPanelPage.vue | 2 +- src/pages/SmsPanelPage.vue | 2 +- src/pages/SmsTenantPage.vue | 188 ++++++++++++++++++++++++++-------- src/styles.css | 82 +++++++-------- src/theme/tokens.ts | 2 +- 7 files changed, 195 insertions(+), 91 deletions(-) diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue index 9a93527..dc56cd7 100644 --- a/src/layouts/AppLayout.vue +++ b/src/layouts/AppLayout.vue @@ -94,7 +94,9 @@ onMounted(loadSmsToggle) diff --git a/src/pages/PluginPanelPage.vue b/src/pages/PluginPanelPage.vue index b130f8f..d0b4614 100644 --- a/src/pages/PluginPanelPage.vue +++ b/src/pages/PluginPanelPage.vue @@ -174,7 +174,7 @@ onMounted(loadTenants) .tenant-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(min(100%, 160px), 1fr)); gap: 10px; } diff --git a/src/pages/ServerPanelPage.vue b/src/pages/ServerPanelPage.vue index d0c7928..991d289 100644 --- a/src/pages/ServerPanelPage.vue +++ b/src/pages/ServerPanelPage.vue @@ -199,7 +199,7 @@ onMounted(loadTenants) .tenant-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(min(100%, 160px), 1fr)); gap: 10px; } diff --git a/src/pages/SmsPanelPage.vue b/src/pages/SmsPanelPage.vue index 3fc99bb..07de3c8 100644 --- a/src/pages/SmsPanelPage.vue +++ b/src/pages/SmsPanelPage.vue @@ -174,7 +174,7 @@ onMounted(loadTenants) .tenant-group-head span { color: #8b95a8; font-size: 0.82rem; } .tenant-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr)); gap: 8px; } .tenant-card { diff --git a/src/pages/SmsTenantPage.vue b/src/pages/SmsTenantPage.vue index 37b4592..6cabd8f 100644 --- a/src/pages/SmsTenantPage.vue +++ b/src/pages/SmsTenantPage.vue @@ -436,11 +436,13 @@ onMounted(async () => {

{{ tenant?.nameFa || 'پنل پیامک' }}

مدیریت کیف پول، تعرفه و گزارش ارسال پیامک

+
+ + {{ tenant.product === 'hamkar' ? 'همکار' : 'خانگی' }} + + {{ tenant.domain }} +
- - {{ tenant.product === 'hamkar' ? 'همکار' : 'خانگی' }} - - {{ tenant.domain }} @@ -594,6 +596,7 @@ onMounted(async () => { v-else-if="periodReport?.byProvider?.length" size="small" :pagination="false" + :scroll="{ x: true }" :data-source="periodReport.byProvider" :columns="providerReportColumns" row-key="provider" @@ -623,6 +626,7 @@ onMounted(async () => { size="small" :loading="chargeReportLoading" :pagination="false" + :scroll="{ x: true }" :data-source="chargeReport.items" :columns="chargeReportColumns" row-key="id" @@ -654,6 +658,7 @@ onMounted(async () => { :loading="logsLoading" :columns="logColumns" :data-source="logsRows" + :scroll="{ x: true }" row-key="id" :pagination="{ current: logsPage, @@ -685,6 +690,7 @@ onMounted(async () => { :loading="ledgerLoading" :columns="ledgerColumns" :data-source="ledgerRows" + :scroll="{ x: true }" row-key="id" :pagination="{ current: ledgerPage, @@ -707,6 +713,32 @@ onMounted(async () => { .sms-page { display: grid; gap: 14px; + width: 100%; + max-width: 100%; + min-width: 0; +} + +.sms-page :deep(.ant-spin-nested-loading), +.sms-page :deep(.ant-spin-container) { + width: 100%; + max-width: 100%; + min-width: 0; +} + +.sms-page :deep(.ant-table-wrapper) { + width: 100%; + max-width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.sms-page :deep(.ant-tabs) { + max-width: 100%; + min-width: 0; +} + +.sms-page :deep(.ant-tabs-nav) { + margin-bottom: 12px; } .page-head { @@ -715,33 +747,52 @@ onMounted(async () => { gap: 8px; flex-wrap: wrap; margin-bottom: 4px; + width: 100%; + min-width: 0; } .page-head-text { - flex: 1; - min-width: 180px; + flex: 1 1 160px; + min-width: 0; +} + +.page-head-meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-top: 8px; + min-width: 0; } .domain { color: #8b95a8; font-size: 0.85rem; direction: ltr; + max-width: 100%; + overflow-wrap: anywhere; + word-break: break-all; } .stat-grid { display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-template-columns: 1fr; gap: 12px; + width: 100%; + min-width: 0; } .stat-card { background: #fff; border: 1px solid #e8eef5; border-radius: 16px; - padding: 16px; + padding: 14px; display: grid; gap: 8px; box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); + min-width: 0; + max-width: 100%; + overflow-wrap: anywhere; } .stat-card span { @@ -750,8 +801,9 @@ onMounted(async () => { } .stat-card strong { - font-size: 1.45rem; + font-size: 1.25rem; color: #0f172a; + overflow-wrap: anywhere; } .stat-card small, @@ -760,6 +812,7 @@ onMounted(async () => { font-size: 0.82rem; font-weight: 600; color: #64748b; + overflow-wrap: anywhere; } .stat-label-row { @@ -767,6 +820,8 @@ onMounted(async () => { justify-content: space-between; gap: 8px; align-items: center; + flex-wrap: wrap; + min-width: 0; } .stat-card.healthy { @@ -805,8 +860,12 @@ onMounted(async () => { background: #fff; border: 1px solid #e8eef5; border-radius: 18px; - padding: 16px; + padding: 14px; box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); + width: 100%; + max-width: 100%; + min-width: 0; + overflow-x: clip; } .panel-card h2 { @@ -818,7 +877,7 @@ onMounted(async () => { .mgmt-row { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: 1fr; gap: 12px; margin-bottom: 12px; } @@ -829,6 +888,8 @@ onMounted(async () => { padding: 14px; display: grid; gap: 12px; + min-width: 0; + max-width: 100%; } .subcard h3 { @@ -856,26 +917,21 @@ onMounted(async () => { .fields-2 { display: grid; gap: 10px; -} - -.inline-actions { - grid-template-columns: 1fr auto; - align-items: center; -} - -.charge-grid { - grid-template-columns: 1fr 1.2fr auto; - align-items: center; + width: 100%; + min-width: 0; } .filter-row { - grid-template-columns: 1fr 1fr auto auto; - align-items: center; margin-bottom: 12px; } -.fields-2 { - grid-template-columns: 1fr 1fr; +.inline-actions, +.charge-grid, +.filter-row, +.threshold-grid, +.fields-2, +.mgmt-row { + grid-template-columns: 1fr; } .block-row { @@ -883,11 +939,18 @@ onMounted(async () => { justify-content: space-between; gap: 12px; align-items: center; + flex-wrap: wrap; border-radius: 14px; border: 1px solid #bbf7d0; background: #f0fdf4; padding: 14px; margin-bottom: 12px; + min-width: 0; +} + +.block-row > div { + flex: 1 1 200px; + min-width: 0; } .block-row.blocked { @@ -905,11 +968,6 @@ onMounted(async () => { margin-bottom: 12px; } -.threshold-grid { - grid-template-columns: 1fr 1fr auto; - align-items: end; -} - .tone-label { display: inline-flex; align-items: center; @@ -930,7 +988,7 @@ onMounted(async () => { .report-summary { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: 1fr; gap: 10px; margin-bottom: 12px; } @@ -941,6 +999,7 @@ onMounted(async () => { padding: 12px; display: grid; gap: 4px; + min-width: 0; } .report-summary span { @@ -952,18 +1011,67 @@ onMounted(async () => { font-variant-numeric: tabular-nums; } -@media (max-width: 1100px) { - .stat-grid, - .mgmt-row, - .charge-grid, - .filter-row, - .threshold-grid, - .fields-2, +.money-input, +.filter-row :deep(.jalali-date-field), +.filter-row :deep(.ant-input), +.filter-row :deep(.ant-btn), +.inline-actions :deep(.ant-input), +.charge-grid :deep(.ant-input), +.charge-grid :deep(.ant-btn) { + width: 100%; + max-width: 100%; + min-width: 0; +} + +@media (min-width: 640px) { + .stat-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } .report-summary { - grid-template-columns: 1fr; + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .fields-2 { + grid-template-columns: 1fr 1fr; + } + .stat-card strong { + font-size: 1.35rem; + } +} + +@media (min-width: 900px) { + .mgmt-row { + grid-template-columns: 1fr 1fr; } .inline-actions { - grid-template-columns: 1fr; + grid-template-columns: 1fr auto; + align-items: center; + } + .charge-grid { + grid-template-columns: 1fr 1.2fr auto; + align-items: center; + } + .filter-row { + grid-template-columns: 1fr 1fr auto auto; + align-items: center; + } + .threshold-grid { + grid-template-columns: 1fr 1fr auto; + align-items: end; + } +} + +@media (min-width: 1100px) { + .stat-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .stat-card { + padding: 16px; + } + .stat-card strong { + font-size: 1.45rem; + } + .panel-card { + padding: 16px; } } diff --git a/src/styles.css b/src/styles.css index 42d396a..7f09cb4 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,73 +1,45 @@ @font-face { - font-family: 'Dana'; - src: url('./assets/fonts/Dana/Dana-Thin.ttf') format('truetype'); - font-weight: 100; - font-style: normal; - font-display: swap; -} -@font-face { - font-family: 'Dana'; - src: url('./assets/fonts/Dana/Dana-ExtraLight.ttf') format('truetype'); + font-family: 'IRANSans'; + src: url('./assets/fonts/iranSans/woff2/IRANSansWeb(FaNum)_UltraLight.woff2') format('woff2'); font-weight: 200; font-style: normal; font-display: swap; } @font-face { - font-family: 'Dana'; - src: url('./assets/fonts/Dana/Dana-Light.ttf') format('truetype'); + font-family: 'IRANSans'; + src: url('./assets/fonts/iranSans/woff2/IRANSansWeb(FaNum)_Light.woff2') format('woff2'); font-weight: 300; font-style: normal; font-display: swap; } @font-face { - font-family: 'Dana'; - src: url('./assets/fonts/Dana/Dana-Regular.ttf') format('truetype'); + font-family: 'IRANSans'; + src: url('./assets/fonts/iranSans/woff2/IRANSansWeb(FaNum).woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; } @font-face { - font-family: 'Dana'; - src: url('./assets/fonts/Dana/Dana-Medium.ttf') format('truetype'); + font-family: 'IRANSans'; + src: url('./assets/fonts/iranSans/woff2/IRANSansWeb(FaNum)_Medium.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; } @font-face { - font-family: 'Dana'; - src: url('./assets/fonts/Dana/Dana-DemiBold.ttf') format('truetype'); - font-weight: 600; - font-style: normal; - font-display: swap; -} -@font-face { - font-family: 'Dana'; - src: url('./assets/fonts/Dana/Dana-Bold.ttf') format('truetype'); + font-family: 'IRANSans'; + src: url('./assets/fonts/iranSans/woff2/IRANSansWeb(FaNum)_Bold.woff2') format('woff2'); font-weight: 700; font-style: normal; font-display: swap; } @font-face { - font-family: 'Dana'; - src: url('./assets/fonts/Dana/Dana-ExtraBold.ttf') format('truetype'); - font-weight: 800; - font-style: normal; - font-display: swap; -} -@font-face { - font-family: 'Dana'; - src: url('./assets/fonts/Dana/Dana-Black.ttf') format('truetype'); + font-family: 'IRANSans'; + src: url('./assets/fonts/iranSans/woff2/IRANSansWeb(FaNum)_Black.woff2') format('woff2'); font-weight: 900; font-style: normal; font-display: swap; } -@font-face { - font-family: 'Dana'; - src: url('./assets/fonts/Dana/Dana-UltraBold.ttf') format('truetype'); - font-weight: 950; - font-style: normal; - font-display: swap; -} :root { --bg: #eef2f7; @@ -83,8 +55,9 @@ --shadow: 0 12px 32px rgba(45, 55, 72, 0.08); --shadow-hover: 0 18px 40px rgba(45, 55, 72, 0.12); --radius: 24px; - --font-dana: 'Dana', Tahoma, sans-serif; - font-family: var(--font-dana); + --font-ui: 'IRANSans', Tahoma, sans-serif; + --font-dana: var(--font-ui); + font-family: var(--font-ui); } * { box-sizing: border-box; } @@ -92,19 +65,25 @@ html, body, #app { margin: 0; min-height: 100%; - font-family: var(--font-dana); + max-width: 100%; + font-family: var(--font-ui); +} + +html { + overflow-x: hidden; } body { color: var(--ink); background: var(--bg); -webkit-font-smoothing: antialiased; + overflow-x: hidden; } button, input, textarea, select, .ant-btn, .ant-input, .ant-select, .ant-menu, .ant-modal, .ant-message, .ant-form, .ant-typography, .ant-tabs { - font-family: var(--font-dana) !important; + font-family: var(--font-ui) !important; } .brand-mark { @@ -115,9 +94,13 @@ button, input, textarea, select, .page-shell { background: var(--panel); border-radius: var(--radius); - padding: 20px; + padding: 16px; color: var(--ink); box-shadow: var(--shadow); + width: 100%; + max-width: 100%; + min-width: 0; + overflow-x: clip; } @media (min-width: 768px) { @@ -126,9 +109,15 @@ button, input, textarea, select, .page-title { margin: 0; - font-size: 1.35rem; + font-size: 1.2rem; color: var(--ink); font-weight: 800; + overflow-wrap: anywhere; + word-break: break-word; +} + +@media (min-width: 768px) { + .page-title { font-size: 1.35rem; } } .page-sub { @@ -136,6 +125,7 @@ button, input, textarea, select, color: var(--ink-soft); font-size: 0.92rem; line-height: 1.7; + overflow-wrap: anywhere; } .dash-modules { diff --git a/src/theme/tokens.ts b/src/theme/tokens.ts index 35b012a..b0f5245 100644 --- a/src/theme/tokens.ts +++ b/src/theme/tokens.ts @@ -3,7 +3,7 @@ import type { ThemeConfig } from 'ant-design-vue/es/config-provider/context' /** Soft light dashboard theme inspired by the reference UI */ export const saTheme: ThemeConfig = { token: { - fontFamily: "'Dana', Tahoma, sans-serif", + fontFamily: "'IRANSans', Tahoma, sans-serif", colorPrimary: '#20c997', colorInfo: '#7c5cfc', colorSuccess: '#20c997',