Download apps/web/src/components/analytics/TimeAnalyticsPanel.tsx from Atmint/cpns: direct link, hf CLI and curl.
- Browser
- Download file 6.41 kB
-
https://huggingface.co/spaces/Atmint/cpns/resolve/main/apps/web/src/components/analytics/TimeAnalyticsPanel.tsx
- Command line
-
hf download hf://spaces/Atmint/cpns/apps/web/src/components/analytics/TimeAnalyticsPanel.tsx
-
curl -L -o TimeAnalyticsPanel.tsx https://huggingface.co/spaces/Atmint/cpns/resolve/main/apps/web/src/components/analytics/TimeAnalyticsPanel.tsx
6.41 kB
| import { | |
| BarChart, | |
| Bar, | |
| XAxis, | |
| YAxis, | |
| CartesianGrid, | |
| Tooltip, | |
| ResponsiveContainer, | |
| } from "recharts"; | |
| import { Card, CardContent, CardHeader, CardTitle } from "@pram/ui/components/card"; | |
| import { formatTime } from "@/lib/time"; | |
| import { formatLabel } from "@/lib/format"; | |
| export interface SectionTimeItem { | |
| sectionTypeName: string; | |
| avgTimeSpentSec: number; | |
| totalTimeSpentSec: number; | |
| } | |
| export interface FormatTimeItem { | |
| format: string; | |
| avgTimeSpentSec: number; | |
| totalTimeSpentSec: number; | |
| } | |
| export interface TimeTrendItem { | |
| date: string; | |
| avgTimeSpentSec: number; | |
| } | |
| interface TimeAnalyticsPanelProps { | |
| sectionTime: SectionTimeItem[] | undefined; | |
| formatTimeData: FormatTimeItem[] | undefined; | |
| timeTrend: TimeTrendItem[] | undefined; | |
| } | |
| export function TimeAnalyticsPanel({ sectionTime, formatTimeData, timeTrend }: TimeAnalyticsPanelProps) { | |
| return ( | |
| <div className="space-y-6"> | |
| <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)]"> | |
| Waktu per Section | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| {sectionTime && sectionTime.length > 0 ? ( | |
| <div className="space-y-3"> | |
| {sectionTime.map((item) => ( | |
| <div key={item.sectionTypeName} className="flex items-center gap-4"> | |
| <div className="w-28 shrink-0 text-sm font-medium text-[var(--clay-black)]"> | |
| {item.sectionTypeName} | |
| </div> | |
| <div className="flex-1"> | |
| <div className="w-full h-2.5 bg-[var(--oat-light)] rounded-full overflow-hidden"> | |
| <div | |
| className="h-full rounded-full bg-[var(--slushie-600)]" | |
| style={{ | |
| width: `${Math.min(100, (item.avgTimeSpentSec / 300) * 100)}%`, | |
| }} | |
| /> | |
| </div> | |
| </div> | |
| <div className="w-20 shrink-0 text-right text-sm text-[var(--warm-charcoal)]"> | |
| {formatTime(item.avgTimeSpentSec)} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| ) : ( | |
| <div className="h-20 flex items-center justify-center text-[var(--warm-charcoal)]"> | |
| Belum ada data. | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| <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)]"> | |
| Waktu per Format Soal | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| {formatTimeData && formatTimeData.length > 0 ? ( | |
| <div className="space-y-3"> | |
| {formatTimeData.map((item) => ( | |
| <div key={item.format} className="flex items-center gap-4"> | |
| <div className="w-32 shrink-0 text-sm font-medium text-[var(--clay-black)] truncate"> | |
| {formatLabel(item.format)} | |
| </div> | |
| <div className="flex-1"> | |
| <div className="w-full h-2.5 bg-[var(--oat-light)] rounded-full overflow-hidden"> | |
| <div | |
| className="h-full rounded-full bg-[var(--ube-600)]" | |
| style={{ | |
| width: `${Math.min(100, (item.avgTimeSpentSec / 120) * 100)}%`, | |
| }} | |
| /> | |
| </div> | |
| </div> | |
| <div className="w-20 shrink-0 text-right text-sm text-[var(--warm-charcoal)]"> | |
| {formatTime(item.avgTimeSpentSec)} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| ) : ( | |
| <div className="h-20 flex items-center justify-center text-[var(--warm-charcoal)]"> | |
| Belum ada data. | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| <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 Waktu per Hari | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| {timeTrend && timeTrend.length > 0 ? ( | |
| <ResponsiveContainer width="100%" height={200}> | |
| <BarChart data={timeTrend} margin={{ top: 5, right: 10, left: -10, bottom: 0 }}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="var(--oat-border)" /> | |
| <XAxis | |
| dataKey="date" | |
| tickFormatter={(v: string) => { | |
| const d = new Date(v); | |
| return `${d.getDate()}/${d.getMonth() + 1}`; | |
| }} | |
| 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} | |
| tickFormatter={(v: number) => `${Math.round(v / 60)}m`} | |
| /> | |
| <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) => [formatTime(typeof value === "number" ? value : 0), "Rata-rata Waktu"]} | |
| /> | |
| <Bar dataKey="avgTimeSpentSec" fill="var(--slushie-600)" radius={[6, 6, 0, 0]} /> | |
| </BarChart> | |
| </ResponsiveContainer> | |
| ) : ( | |
| <div className="h-48 flex items-center justify-center text-[var(--warm-charcoal)]"> | |
| Belum ada data. | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| </div> | |
| ); | |
| } | |