diff --git a/src/food-market.api/Controllers/SuperAdmin/SuperAdminController.cs b/src/food-market.api/Controllers/SuperAdmin/SuperAdminController.cs index cda385c..c04f68a 100644 --- a/src/food-market.api/Controllers/SuperAdmin/SuperAdminController.cs +++ b/src/food-market.api/Controllers/SuperAdmin/SuperAdminController.cs @@ -29,11 +29,14 @@ public async Task> GetSetupStatus(CancellationToken public record DashboardStats( int TotalOrgs, int ActiveOrgs, int ArchivedOrgs, int TotalUsers, int ActiveUsers, - int TotalProducts, int TotalSuppliesThisMonth); + int RegistrationsLast30Days); [HttpGet("dashboard")] public async Task> Dashboard(CancellationToken ct) { + // Метрики SuperAdmin'а — кабинет SaaS-владельца, не операционные + // показатели магазинов. Биллинговые KPI (MRR, должники, платящие) + // считаем на UI как заглушки — отдельный модуль подписки в Phase 4+. var monthAgo = DateTime.UtcNow.AddDays(-30); return new DashboardStats( TotalOrgs: await _db.Organizations.IgnoreQueryFilters().CountAsync(ct), @@ -41,8 +44,7 @@ public async Task> Dashboard(CancellationToken ct) ArchivedOrgs: await _db.Organizations.IgnoreQueryFilters().CountAsync(o => o.IsArchived, ct), TotalUsers: await _db.Users.CountAsync(ct), ActiveUsers: await _db.Users.CountAsync(u => u.IsActive, ct), - TotalProducts: await _db.Products.IgnoreQueryFilters().CountAsync(ct), - TotalSuppliesThisMonth: await _db.Supplies.IgnoreQueryFilters().CountAsync(s => s.Date >= monthAgo, ct)); + RegistrationsLast30Days: await _db.Organizations.IgnoreQueryFilters().CountAsync(o => o.CreatedAt >= monthAgo, ct)); } public record AuditRow( diff --git a/src/food-market.web/src/pages/SuperAdminDashboardPage.tsx b/src/food-market.web/src/pages/SuperAdminDashboardPage.tsx index 5d23ff6..adf77f3 100644 --- a/src/food-market.web/src/pages/SuperAdminDashboardPage.tsx +++ b/src/food-market.web/src/pages/SuperAdminDashboardPage.tsx @@ -1,15 +1,17 @@ import { useQuery } from '@tanstack/react-query' import { Link } from 'react-router-dom' import { - Building2, Users, Package, ShoppingCart, FileClock, + Building2, Users, FileClock, CheckCircle2, AlertCircle, Activity, Inbox, ShieldCheck, + CreditCard, TrendingUp, AlertTriangle, UserPlus, } from 'lucide-react' import { api } from '@/lib/api' +import { cn } from '@/lib/utils' interface DashboardStats { totalOrgs: number; activeOrgs: number; archivedOrgs: number totalUsers: number; activeUsers: number - totalProducts: number; totalSuppliesThisMonth: number + registrationsLast30Days: number } interface AuditRow { @@ -19,26 +21,36 @@ interface AuditRow { const fmt = new Intl.NumberFormat('ru') -function Kpi({ icon: Icon, label, value, hint, accent = 'indigo' }: { +function Kpi({ icon: Icon, label, value, hint, accent = 'indigo', muted = false }: { icon: React.ComponentType<{ className?: string }> label: string; value: string | number; hint?: string - accent?: 'indigo' | 'emerald' | 'amber' | 'sky' + accent?: 'indigo' | 'emerald' | 'amber' | 'sky' | 'rose' | 'violet' + /** «Скоро» — заглушка под будущий биллинг. Тонировка фона сильнее, + * но иконка остаётся цветной чтобы было ясно «здесь будет данные». */ + muted?: boolean }) { const accents: Record = { indigo: 'bg-indigo-100 text-indigo-600 dark:bg-indigo-900/40 dark:text-indigo-300', emerald: 'bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-300', amber: 'bg-amber-100 text-amber-600 dark:bg-amber-900/40 dark:text-amber-300', sky: 'bg-sky-100 text-sky-600 dark:bg-sky-900/40 dark:text-sky-300', + rose: 'bg-rose-100 text-rose-600 dark:bg-rose-900/40 dark:text-rose-300', + violet: 'bg-violet-100 text-violet-600 dark:bg-violet-900/40 dark:text-violet-300', } return ( -
+
{label}
-
{value}
+
{value}
{hint &&
{hint}
}
@@ -98,14 +110,25 @@ export function SuperAdminDashboardPage() {
- {/* KPI cards */} + {/* KPI: SaaS-владелец платформы. Главный ряд — клиентская база + * и биллинг (последние 3 — заглушки до Phase 4 «Подписки»). + * Второй ряд — инфраструктура учёток. Операционные метрики + * магазинов (товары/приёмки) выкинуты — это для tenant-дашборда. */}
+ + + +
+
- - +
{/* Three side-by-side blocks */}