2026-04-09 10:01:23 +08:00
|
|
|
"use client"
|
|
|
|
|
|
|
|
|
|
import { Area, AreaChart, Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
|
|
|
|
|
|
|
|
import type { DashboardStatusDistributionItem, DashboardTrendItem } from "@/lib/api/dashboard"
|
|
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
|
|
|
|
} from "@/components/ui/card"
|
|
|
|
|
import {
|
|
|
|
|
ChartContainer,
|
|
|
|
|
ChartTooltip,
|
|
|
|
|
ChartTooltipContent,
|
|
|
|
|
type ChartConfig,
|
|
|
|
|
} from "@/components/ui/chart"
|
2026-05-25 12:06:15 +08:00
|
|
|
import { useI18n } from "@/i18n/provider"
|
2026-04-09 10:01:23 +08:00
|
|
|
|
|
|
|
|
type TrendPanelProps = {
|
|
|
|
|
title: string
|
|
|
|
|
description: string
|
|
|
|
|
trend: DashboardTrendItem[]
|
|
|
|
|
distribution: DashboardStatusDistributionItem[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function TrendPanel({
|
|
|
|
|
title,
|
|
|
|
|
description,
|
|
|
|
|
trend,
|
|
|
|
|
distribution,
|
|
|
|
|
}: TrendPanelProps) {
|
2026-05-25 12:06:15 +08:00
|
|
|
const t = useI18n()
|
|
|
|
|
const trendConfig = {
|
|
|
|
|
newCount: {
|
|
|
|
|
label: t("dashboardHome.chartNew"),
|
|
|
|
|
color: "hsl(24 95% 53%)",
|
|
|
|
|
},
|
|
|
|
|
closedCount: {
|
|
|
|
|
label: t("dashboardHome.chartClosed"),
|
|
|
|
|
color: "hsl(190 95% 39%)",
|
|
|
|
|
},
|
|
|
|
|
} satisfies ChartConfig
|
|
|
|
|
const distributionConfig = {
|
|
|
|
|
count: {
|
|
|
|
|
label: t("dashboardHome.chartCount"),
|
|
|
|
|
theme: {
|
|
|
|
|
light: "hsl(222 47% 11%)",
|
|
|
|
|
dark: "hsl(210 40% 98%)",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
} satisfies ChartConfig
|
|
|
|
|
const localizedDistribution = distribution.map((item) => ({
|
|
|
|
|
...item,
|
|
|
|
|
label: getStatusLabel(item.status, item.label, t),
|
|
|
|
|
}))
|
|
|
|
|
|
2026-04-09 10:01:23 +08:00
|
|
|
return (
|
|
|
|
|
<div className="grid gap-4 xl:grid-cols-[1.5fr_0.9fr]">
|
2026-06-21 10:09:52 +08:00
|
|
|
<Card className="rounded-md shadow-none">
|
2026-04-09 10:01:23 +08:00
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>{title}</CardTitle>
|
|
|
|
|
<CardDescription>{description}</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<ChartContainer config={trendConfig} className="h-72 w-full">
|
|
|
|
|
<AreaChart data={trend}>
|
|
|
|
|
<CartesianGrid vertical={false} />
|
|
|
|
|
<XAxis dataKey="date" tickLine={false} axisLine={false} tickMargin={8} />
|
|
|
|
|
<ChartTooltip content={<ChartTooltipContent />} />
|
|
|
|
|
<Area
|
|
|
|
|
type="monotone"
|
|
|
|
|
dataKey="newCount"
|
|
|
|
|
stroke="var(--color-newCount)"
|
|
|
|
|
fill="var(--color-newCount)"
|
|
|
|
|
fillOpacity={0.18}
|
|
|
|
|
strokeWidth={2}
|
|
|
|
|
/>
|
|
|
|
|
<Area
|
|
|
|
|
type="monotone"
|
|
|
|
|
dataKey="closedCount"
|
|
|
|
|
stroke="var(--color-closedCount)"
|
|
|
|
|
fill="var(--color-closedCount)"
|
|
|
|
|
fillOpacity={0.08}
|
|
|
|
|
strokeWidth={2}
|
|
|
|
|
/>
|
|
|
|
|
</AreaChart>
|
|
|
|
|
</ChartContainer>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
|
2026-06-21 10:09:52 +08:00
|
|
|
<Card className="rounded-md shadow-none">
|
2026-04-09 10:01:23 +08:00
|
|
|
<CardHeader>
|
2026-05-25 12:06:15 +08:00
|
|
|
<CardTitle>{t("dashboardHome.statusDistribution")}</CardTitle>
|
|
|
|
|
<CardDescription>{t("dashboardHome.statusDistributionDescription")}</CardDescription>
|
2026-04-09 10:01:23 +08:00
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
<ChartContainer config={distributionConfig} className="h-72 w-full">
|
2026-05-25 12:06:15 +08:00
|
|
|
<BarChart data={localizedDistribution} layout="vertical" margin={{ left: 20 }}>
|
2026-04-09 10:01:23 +08:00
|
|
|
<CartesianGrid horizontal={false} />
|
|
|
|
|
<YAxis
|
|
|
|
|
type="category"
|
|
|
|
|
dataKey="label"
|
|
|
|
|
tickLine={false}
|
|
|
|
|
axisLine={false}
|
|
|
|
|
width={64}
|
|
|
|
|
/>
|
|
|
|
|
<XAxis type="number" hide />
|
|
|
|
|
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
|
|
|
|
|
<Bar
|
|
|
|
|
dataKey="count"
|
|
|
|
|
fill="var(--color-count)"
|
|
|
|
|
radius={[0, 8, 8, 0]}
|
|
|
|
|
/>
|
|
|
|
|
</BarChart>
|
|
|
|
|
</ChartContainer>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-05-25 12:06:15 +08:00
|
|
|
|
|
|
|
|
function getStatusLabel(
|
|
|
|
|
status: number,
|
|
|
|
|
fallback: string,
|
|
|
|
|
t: (key: string) => string
|
|
|
|
|
) {
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 1:
|
|
|
|
|
return t("dashboardHome.statusAiServing")
|
|
|
|
|
case 2:
|
|
|
|
|
return t("dashboardHome.statusPending")
|
|
|
|
|
case 3:
|
|
|
|
|
return t("dashboardHome.statusActive")
|
|
|
|
|
case 4:
|
|
|
|
|
return t("dashboardHome.statusClosed")
|
|
|
|
|
default:
|
|
|
|
|
return fallback
|
|
|
|
|
}
|
|
|
|
|
}
|