Download apps/web/src/components/analytics/ScoreTrendChart.tsx from Atmint/cpns: direct link, hf CLI and curl.
- Browser
- Download file 3.5 kB
-
https://huggingface.co/spaces/Atmint/cpns/resolve/main/apps/web/src/components/analytics/ScoreTrendChart.tsx
- Command line
-
hf download hf://spaces/Atmint/cpns/apps/web/src/components/analytics/ScoreTrendChart.tsx
-
curl -L -o ScoreTrendChart.tsx https://huggingface.co/spaces/Atmint/cpns/resolve/main/apps/web/src/components/analytics/ScoreTrendChart.tsx
3.5 kB
| import { | |
| AreaChart, | |
| Area, | |
| XAxis, | |
| YAxis, | |
| CartesianGrid, | |
| Tooltip, | |
| ResponsiveContainer, | |
| } from "recharts"; | |
| import { Card, CardContent, CardHeader, CardTitle } from "@pram/ui/components/card"; | |
| interface TrendPoint { | |
| date: string; | |
| attempts: number; | |
| avgScorePct: number; | |
| } | |
| interface ScoreTrendChartProps { | |
| data: TrendPoint[] | undefined; | |
| } | |
| function formatDateLabel(dateStr: string) { | |
| const d = new Date(dateStr); | |
| return `${d.getDate()}/${d.getMonth() + 1}`; | |
| } | |
| export function ScoreTrendChart({ data }: ScoreTrendChartProps) { | |
| if (!data || data.length === 0) { | |
| return ( | |
| <Card className="clay-shadow bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[var(--radius-xl)]"> | |
| <CardHeader> | |
| <CardTitle className="text-lg font-headline font-bold text-[var(--clay-black)]"> | |
| Tren Skor | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent className="h-64 flex items-center justify-center text-[var(--warm-charcoal)]"> | |
| Belum ada data tren. | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| const chartData = data.map((d) => ({ | |
| ...d, | |
| label: formatDateLabel(d.date), | |
| })); | |
| return ( | |
| <Card className="clay-shadow bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[var(--radius-xl)]"> | |
| <CardHeader> | |
| <CardTitle className="text-lg font-headline font-bold text-[var(--clay-black)]"> | |
| Tren Skor (30 Hari) | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <ResponsiveContainer width="100%" height={256}> | |
| <AreaChart data={chartData} margin={{ top: 5, right: 10, left: -10, bottom: 0 }}> | |
| <defs> | |
| <linearGradient id="scoreGradient" x1="0" y1="0" x2="0" y2="1"> | |
| <stop offset="5%" stopColor="var(--matcha-600)" stopOpacity={0.2} /> | |
| <stop offset="95%" stopColor="var(--matcha-600)" stopOpacity={0} /> | |
| </linearGradient> | |
| </defs> | |
| <CartesianGrid strokeDasharray="3 3" stroke="var(--oat-border)" /> | |
| <XAxis | |
| dataKey="label" | |
| tick={{ fontSize: 12, fill: "var(--warm-charcoal)" }} | |
| axisLine={{ stroke: "var(--oat-border)" }} | |
| tickLine={false} | |
| /> | |
| <YAxis | |
| tick={{ fontSize: 12, fill: "var(--warm-charcoal)" }} | |
| axisLine={false} | |
| tickLine={false} | |
| domain={[0, 100]} | |
| unit="%" | |
| /> | |
| <Tooltip | |
| contentStyle={{ | |
| backgroundColor: "var(--pure-white)", | |
| border: "2px solid var(--oat-border)", | |
| borderRadius: "var(--radius-lg)", | |
| fontSize: 13, | |
| }} | |
| formatter={(value: unknown, name: unknown, _item: unknown, _index: unknown, _payload: unknown) => { | |
| const num = typeof value === "number" ? value : 0; | |
| const n = typeof name === "string" ? name : ""; | |
| if (n === "avgScorePct") return [`${num}%`, "Rata-rata Skor"]; | |
| if (n === "attempts") return [num, "Latihan"]; | |
| return [num, n]; | |
| }} | |
| /> | |
| <Area | |
| type="monotone" | |
| dataKey="avgScorePct" | |
| stroke="var(--matcha-600)" | |
| strokeWidth={2} | |
| fill="url(#scoreGradient)" | |
| /> | |
| </AreaChart> | |
| </ResponsiveContainer> | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |