Move business logic out of App Router into module packages, add boundary validation scripts, and keep all routes as thin re-exports without changing URLs or API behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
75 lines
3.5 KiB
TypeScript
75 lines
3.5 KiB
TypeScript
"use client";
|
||
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import Link from "next/link";
|
||
import { sportsCenterApi } from "@/modules/sports-center/services/sports-center-api";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
import { useSportsCenterCapabilities } from "@/modules/sports-center/hooks/useSportsCenterCapabilities";
|
||
import { SportsCenterPageLoader, SportsCenterPageError } from "@/modules/sports-center/pages/shared";
|
||
import { PageHeader, StatCard, Card, CardContent, Button, Badge } from "@/components/ds";
|
||
import { SportsCenterBreadcrumbs } from "@/modules/sports-center/components/SportsCenterBreadcrumbs";
|
||
|
||
export function OwnerDashboard() {
|
||
const { tenantId } = useTenantId();
|
||
const caps = useSportsCenterCapabilities();
|
||
|
||
const countsQ = useQuery({
|
||
queryKey: ["sports-center", tenantId, "dashboard-counts"],
|
||
queryFn: async () => {
|
||
const [members, memberships, coaches, sessions, attendance] = await Promise.all([
|
||
sportsCenterApi.members.list(tenantId!, { page: 1, page_size: 500 }),
|
||
sportsCenterApi.memberships.list(tenantId!, { page: 1, page_size: 500 }),
|
||
sportsCenterApi.coaches.list(tenantId!, { page: 1, page_size: 500 }),
|
||
caps.data?.features?.booking_engine
|
||
? sportsCenterApi.sessions.list(tenantId!, { sports_center_id: "", page: 1, page_size: 500 }).catch(() => [])
|
||
: Promise.resolve([]),
|
||
Promise.resolve([]),
|
||
]);
|
||
const activeMembers = members.filter((m) => m.status === "active").length;
|
||
const activeMemberships = memberships.filter((m) => m.status === "active").length;
|
||
return {
|
||
members: members.length,
|
||
activeMembers,
|
||
memberships: memberships.length,
|
||
activeMemberships,
|
||
coaches: coaches.length,
|
||
sessions: sessions.length,
|
||
attendance: attendance.length,
|
||
};
|
||
},
|
||
enabled: !!tenantId && !caps.isLoading,
|
||
});
|
||
|
||
if (!tenantId || countsQ.isLoading || caps.isLoading) return <SportsCenterPageLoader />;
|
||
if (countsQ.error) return <SportsCenterPageError error={countsQ.error} onRetry={() => countsQ.refetch()} />;
|
||
|
||
const c = countsQ.data!;
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<SportsCenterBreadcrumbs current="داشبورد اجرایی" />
|
||
<PageHeader
|
||
title="داشبورد مدیریتی"
|
||
description="نمای اجرایی اعضا، عضویتها، حضور و عملیات روزانه."
|
||
actions={<Badge tone="success">v{caps.data?.version}</Badge>}
|
||
/>
|
||
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||
<StatCard label="کل اعضا" value={String(c.members)} hint={`${c.activeMembers} فعال`} />
|
||
<StatCard label="عضویتها" value={String(c.memberships)} hint={`${c.activeMemberships} فعال`} />
|
||
<StatCard label="مربیان" value={String(c.coaches)} hint="Staff" />
|
||
<StatCard label="جلسات" value={String(c.sessions)} hint="Sessions" />
|
||
</div>
|
||
<Card>
|
||
<CardContent className="flex flex-wrap gap-2 p-4">
|
||
<Link href="/sports-center/members"><Button variant="outline">اعضا</Button></Link>
|
||
<Link href="/sports-center/memberships"><Button variant="outline">عضویتها</Button></Link>
|
||
<Link href="/sports-center/attendance"><Button variant="outline">حضور</Button></Link>
|
||
<Link href="/sports-center/analytics"><Button variant="outline">آنالیتیکس</Button></Link>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default OwnerDashboard;
|