Files
iddaai-fe/src/components/admin/admin-content.tsx
T
fahricansecer fc7a1ba567
Deploy Iddaai Frontend / build-and-deploy (push) Successful in 4m0s
first
2026-04-16 13:36:34 +03:00

284 lines
8.9 KiB
TypeScript

"use client";
import {
Box,
Flex,
Heading,
Text,
SimpleGrid,
Card,
VStack,
HStack,
Badge,
Spinner,
Button,
Separator,
} from "@chakra-ui/react";
import { useTranslations } from "next-intl";
import { useColorModeValue } from "@/components/ui/color-mode";
import {
SlideUp,
StaggerContainer,
StaggerItem,
AnimatedCounter,
} from "@/components/motion";
import { useAdminAnalytics, useAdminUsers } from "@/lib/api/admin/use-hooks";
import type { AdminUserDto, AnalyticsOverviewDto } from "@/lib/api/admin/types";
import { LuUsers, LuChartBar, LuActivity, LuShield } from "react-icons/lu";
import { useState } from "react";
type AdminTab = "overview" | "users";
// ========================
// Admin Stat Card
// ========================
interface AdminStatProps {
label: string;
value: number;
icon: React.ReactNode;
colorPalette: string;
}
function AdminStat({ label, value, icon, colorPalette }: AdminStatProps) {
const cardBg = useColorModeValue("white", "gray.800");
const borderColor = useColorModeValue("gray.100", "gray.700");
return (
<Card.Root bg={cardBg} borderColor={borderColor} borderRadius="xl">
<Card.Body>
<HStack gap={4}>
<Flex
boxSize="48px"
bg={`${colorPalette}.subtle`}
borderRadius="xl"
align="center"
justify="center"
color={`${colorPalette}.fg`}
fontSize="xl"
>
{icon}
</Flex>
<VStack gap={0} align="flex-start">
<Text fontSize="2xl" fontWeight="900" lineHeight="1">
<AnimatedCounter value={value} />
</Text>
<Text fontSize="xs" color="fg.muted" fontWeight="medium">
{label}
</Text>
</VStack>
</HStack>
</Card.Body>
</Card.Root>
);
}
// ========================
// Admin Content
// ========================
export default function AdminContent() {
const t = useTranslations("admin");
const tCommon = useTranslations("common");
const [activeTab, setActiveTab] = useState<AdminTab>("overview");
const cardBg = useColorModeValue("white", "gray.800");
const borderColor = useColorModeValue("gray.100", "gray.700");
const { data: analyticsData, isLoading: analyticsLoading } =
useAdminAnalytics();
const { data: usersData, isLoading: usersLoading } = useAdminUsers();
const analytics = analyticsData?.data as AnalyticsOverviewDto | undefined;
const users = (usersData?.data as AdminUserDto[] | undefined) ?? [];
const tabs: { key: AdminTab; label: string }[] = [
{ key: "overview", label: t("overview") },
{ key: "users", label: t("user-management") },
];
const getUserDisplayName = (user: AdminUserDto) => {
if (user.firstName && user.lastName)
return `${user.firstName} ${user.lastName}`;
if (user.firstName) return user.firstName;
return user.email.split("@")[0];
};
return (
<SlideUp>
<Box>
<Flex justify="space-between" align="center" mb={6}>
<VStack gap={1} align="flex-start">
<Heading as="h1" size="xl" fontWeight="bold">
{t("title")}
</Heading>
<Text color="fg.muted" fontSize="sm">
{t("subtitle")}
</Text>
</VStack>
<Badge
colorPalette="red"
variant="solid"
px={3}
py={1}
borderRadius="full"
>
<LuShield />
{t("admin-badge")}
</Badge>
</Flex>
{/* Tabs */}
<HStack gap={2} mb={6}>
{tabs.map((tab) => (
<Button
key={tab.key}
variant={activeTab === tab.key ? "solid" : "outline"}
colorPalette={activeTab === tab.key ? "primary" : "gray"}
size="sm"
borderRadius="full"
onClick={() => setActiveTab(tab.key)}
>
{tab.label}
</Button>
))}
</HStack>
{/* Overview Tab */}
{activeTab === "overview" &&
(analyticsLoading ? (
<Flex justify="center" py={16}>
<Spinner size="lg" color="primary.500" />
</Flex>
) : (
<StaggerContainer>
<SimpleGrid columns={{ base: 2, md: 4 }} gap={4} mb={8}>
<StaggerItem>
<AdminStat
label={t("total-users")}
value={analytics?.totalUsers ?? 0}
icon={<LuUsers />}
colorPalette="primary"
/>
</StaggerItem>
<StaggerItem>
<AdminStat
label={t("total-predictions")}
value={analytics?.totalPredictions ?? 0}
icon={<LuChartBar />}
colorPalette="green"
/>
</StaggerItem>
<StaggerItem>
<AdminStat
label={t("active-users")}
value={analytics?.activeUsers ?? 0}
icon={<LuActivity />}
colorPalette="orange"
/>
</StaggerItem>
<StaggerItem>
<AdminStat
label={t("total-coupons")}
value={analytics?.totalCoupons ?? 0}
icon={<LuShield />}
colorPalette="purple"
/>
</StaggerItem>
</SimpleGrid>
</StaggerContainer>
))}
{/* Users Tab */}
{activeTab === "users" &&
(usersLoading ? (
<Flex justify="center" py={16}>
<Spinner size="lg" color="primary.500" />
</Flex>
) : users.length > 0 ? (
<Card.Root bg={cardBg} borderColor={borderColor} borderRadius="xl">
<Card.Body>
<VStack gap={0} align="stretch">
{/* Table Header */}
<Flex
px={4}
py={2}
bg="bg.muted"
borderRadius="lg"
mb={2}
fontWeight="semibold"
fontSize="xs"
color="fg.muted"
>
<Text flex={2}>{t("user-name")}</Text>
<Text flex={2}>{t("user-email")}</Text>
<Text flex={1} textAlign="center">
{t("user-role")}
</Text>
<Text flex={1} textAlign="center">
{t("user-status")}
</Text>
</Flex>
{/* User Rows */}
{users.map((user: AdminUserDto, idx: number) => (
<Box key={user.id ?? idx}>
{idx > 0 && <Separator />}
<Flex
px={4}
py={3}
align="center"
_hover={{ bg: "bg.muted" }}
borderRadius="lg"
>
<Text
flex={2}
fontSize="sm"
fontWeight="medium"
truncate
>
{getUserDisplayName(user)}
</Text>
<Text flex={2} fontSize="sm" color="fg.muted" truncate>
{user.email}
</Text>
<Flex flex={1} justify="center">
<Badge
colorPalette={
user.role === "ADMIN" ? "red" : "gray"
}
variant="subtle"
fontSize="2xs"
borderRadius="full"
>
{user.role || "User"}
</Badge>
</Flex>
<Flex flex={1} justify="center">
<Badge
colorPalette={user.isActive ? "green" : "gray"}
variant="subtle"
fontSize="2xs"
borderRadius="full"
>
{user.isActive
? tCommon("active")
: tCommon("inactive")}
</Badge>
</Flex>
</Flex>
</Box>
))}
</VStack>
</Card.Body>
</Card.Root>
) : (
<Flex justify="center" py={16}>
<Text color="fg.muted">{t("no-users")}</Text>
</Flex>
))}
</Box>
</SlideUp>
);
}