diff --git a/apps/admin-panel/src/views/Dashboard.vue b/apps/admin-panel/src/views/Dashboard.vue index ea0e077..e776225 100644 --- a/apps/admin-panel/src/views/Dashboard.vue +++ b/apps/admin-panel/src/views/Dashboard.vue @@ -80,12 +80,12 @@

روند سفارش‌ها

-

۳۰ روز گذشته، تفکیک تک و عمده

+

{{ isSuper ? '۳۰ روز گذشته، تفکیک تک و عمده' : '۳۰ روز گذشته' }}

-
+
تک فروشی {{ fmt(retailTotal) }} @@ -105,7 +105,7 @@ />
-
+

تفکیک عمده / تک

@@ -295,10 +295,14 @@ import { } from '@ant-design/icons-vue'; import { jdate, price } from '../utils/filters'; import { useLayoutStore } from '../stores/layout'; +import { useUsersStore } from '../stores/users'; import api from '../api/client'; const loading = ref(false); const layout = useLayoutStore(); +// Retail («تک فروشی») figures are for the super admin only; the API also omits them. +const users = useUsersStore(); +const isSuper = computed(() => users.isSuperAdmin); const chartHeight = computed(() => (layout.isMobile ? 240 : 300)); const donutHeight = computed(() => (layout.isMobile ? 220 : 240)); @@ -419,14 +423,13 @@ const wholesaleTotal = computed(() => ); const trendSeries = computed(() => { const trend: OrdersTrendPoint[] = data.ordersTrend ?? []; - return [ - { name: 'تک فروشی', data: trend.map((d) => d.retail) }, - { name: 'عمده فروشی', data: trend.map((d) => d.wholesale) }, - ]; + const wholesale = { name: 'عمده فروشی', data: trend.map((d) => d.wholesale) }; + if (!isSuper.value) return [wholesale]; + return [{ name: 'تک فروشی', data: trend.map((d) => d.retail) }, wholesale]; }); const trendChartOptions = computed(() => ({ chart: { ...BASE_CHART, stacked: trendType.value === 'bar' }, - colors: [COLOR_PRIMARY, COLOR_SUCCESS], + colors: isSuper.value ? [COLOR_PRIMARY, COLOR_SUCCESS] : [COLOR_SUCCESS], dataLabels: { enabled: false }, stroke: trendType.value === 'area' ? { curve: 'smooth', width: 2.5 } : { width: 0 }, fill: diff --git a/apps/api/src/modules/admin-dashboard/admin-dashboard.controller.ts b/apps/api/src/modules/admin-dashboard/admin-dashboard.controller.ts index d5068cd..0b222ce 100644 --- a/apps/api/src/modules/admin-dashboard/admin-dashboard.controller.ts +++ b/apps/api/src/modules/admin-dashboard/admin-dashboard.controller.ts @@ -1,6 +1,8 @@ import { Controller, Get, Query } from '@nestjs/common'; import { AdminDashboardService } from './admin-dashboard.service'; -import { RequirePermission } from '../auth/guards'; +import { CurrentAdmin, RequirePermission } from '../auth/guards'; +import type { AuthenticatedAdmin } from '../auth/jwt.strategy'; +import { isSuperAdmin } from '../auth/super-admin'; /** * Legacy shipped two dashboards behind two rids: @@ -18,8 +20,8 @@ export class AdminDashboardController { @Get('overview') @RequirePermission('dashboard.view') - overview() { - return this.dashboard.overview(); + overview(@CurrentAdmin() admin: AuthenticatedAdmin) { + return this.dashboard.overview(isSuperAdmin(admin.mobile)); } /** @deprecated superseded by GET overview; kept as an alias. */ diff --git a/apps/api/src/modules/admin-dashboard/admin-dashboard.service.ts b/apps/api/src/modules/admin-dashboard/admin-dashboard.service.ts index 6cc96d7..29eda8b 100644 --- a/apps/api/src/modules/admin-dashboard/admin-dashboard.service.ts +++ b/apps/api/src/modules/admin-dashboard/admin-dashboard.service.ts @@ -37,8 +37,12 @@ export class AdminDashboardService { status: { in: [OrderStatus.DELIVERED_TO_AGENT, OrderStatus.DELIVERED_TO_CUSTOMER] }, }; - /** The single merged dashboard endpoint (`GET admin/dashboard/overview`). */ - async overview() { + /** + * The single merged dashboard endpoint (`GET admin/dashboard/overview`). + * Retail («تک فروشی») figures are only built for the super admin; for anyone + * else they never leave the server. + */ + async overview(viewerIsSuperAdmin = false) { const since30 = this.daysAgo(30); const since7 = this.daysAgo(7); @@ -111,7 +115,11 @@ export class AdminDashboardService { if (o.channel === 'WHOLESALE') bucket.wholesale += 1; else bucket.retail += 1; } - const ordersTrend = [...trendByDay.entries()].map(([date, v]) => ({ date, ...v })); + // Non-super admins get wholesale only: their `total` is the wholesale count + // and there is no `retail` field, so the retail number cannot be derived. + const ordersTrend = [...trendByDay.entries()].map(([date, v]) => + viewerIsSuperAdmin ? { date, ...v } : { date, total: v.wholesale, wholesale: v.wholesale }, + ); const statusLabels: Record = { SUSPENDED: 'معلق (سقف اعتبار)', @@ -130,12 +138,14 @@ export class AdminDashboardService { })); const channelLabels: Record = { RETAIL: 'تک فروشی', WHOLESALE: 'عمده فروشی' }; - const channelBreakdown = channelGroups.map((g) => ({ - channel: g.channel, - label: channelLabels[g.channel] ?? g.channel, - count: g._count, - grams: g._sum.totalGoldWeightGrams ?? new Prisma.Decimal(0), - })); + const channelBreakdown = viewerIsSuperAdmin + ? channelGroups.map((g) => ({ + channel: g.channel, + label: channelLabels[g.channel] ?? g.channel, + count: g._count, + grams: g._sum.totalGoldWeightGrams ?? new Prisma.Decimal(0), + })) + : []; const productTotals = new Map(); for (const it of topProductRows) {