Untitled
unknown
plain_text
a month ago
23 kB
3
Indexable
"use client" import { useState } from "react" import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts" import { AppSidebar } from "@/components/app-sidebar" import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar" import { useTranslation, type SupportedLanguages } from "@/utils/translations" import { useSettings } from "@/hooks/useSettings" import { useMetrics } from "@/hooks/useMetrics" import { useBinConfig } from "@/hooks/use-bin-config" import { useFillPredictions } from "@/hooks/useFillPredictions" import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from "@/components/ui/breadcrumb" import { Separator } from "@/components/ui/separator" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { DataTable } from "@/components/ui/data-table" import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent } from "@/components/ui/chart" export default function SortingAnalyticsPage() { const { settings } = useSettings() const { t } = useTranslation((settings?.language as SupportedLanguages) || "EN") const { metrics, loading, error } = useMetrics() const { bins } = useBinConfig() const { predictions } = useFillPredictions() const [selectedTimeRange, setSelectedTimeRange] = useState("daily") if (loading) { return ( <SidebarProvider> <div className="flex h-screen w-full"> <AppSidebar /> <div className="flex-1 overflow-auto"> <header className="flex shrink-0 items-center gap-2 border-b px-4 py-6"> <SidebarTrigger /> <Separator orientation="vertical" className="mx-2 h-4" /> <Breadcrumb> <BreadcrumbList> <BreadcrumbItem className="hidden sm:block"> <BreadcrumbLink href="/">{t('navigation.home')}</BreadcrumbLink> </BreadcrumbItem> <BreadcrumbSeparator className="hidden sm:block" /> <BreadcrumbItem> <BreadcrumbPage>{t('navigation.sorting')}</BreadcrumbPage> </BreadcrumbItem> </BreadcrumbList> </Breadcrumb> </header> <main className="container mx-auto p-4"> <div className="flex items-center justify-center h-64"> <p className="text-lg">{t('common.loading')}</p> </div> </main> </div> </div> </SidebarProvider> ) } if (error || !metrics) { return ( <SidebarProvider> <div className="flex h-screen w-full"> <AppSidebar /> <div className="flex-1 overflow-auto"> <header className="flex shrink-0 items-center gap-2 border-b px-4 py-6"> <SidebarTrigger /> <Separator orientation="vertical" className="mx-2 h-4" /> <Breadcrumb> <BreadcrumbList> <BreadcrumbItem className="hidden sm:block"> <BreadcrumbLink href="/">{t('navigation.home')}</BreadcrumbLink> </BreadcrumbItem> <BreadcrumbSeparator className="hidden sm:block" /> <BreadcrumbItem> <BreadcrumbPage>{t('navigation.sorting')}</BreadcrumbPage> </BreadcrumbItem> </BreadcrumbList> </Breadcrumb> </header> <main className="container mx-auto p-4"> <div className="flex items-center justify-center h-64"> <p className="text-lg text-red-500">{error || t('common.error')}</p> </div> </main> </div> </div> </SidebarProvider> ) } // Prepare data for Items per bin type chart const binItemsData = Object.entries(metrics.basic_metrics.items_sorted_per_bin).map(([binId, count]) => { const bin = bins?.find(b => b.id === binId) || { name: binId, color: "#666666", color_dark: "#999999" } return { bin: bin.name, count: count, binId, fill: settings?.theme === 'dark' ? bin.color_dark : bin.color } }).sort((a, b) => b.count - a.count) // Prepare data for API vs Local classification const apiVsLocalData = [ { name: "API", value: metrics.basic_metrics.api_vs_local_usage.api, fill: settings?.theme === 'dark' ? "#4f46e5" : "#6366f1" // Indigo }, { name: "Local", value: metrics.basic_metrics.api_vs_local_usage.local, fill: settings?.theme === 'dark' ? "#2563eb" : "#3b82f6" // Blue } ] // Prepare data for time-based charts (daily/weekly/monthly) const timeSeriesData = { daily: Object.entries(metrics.time_metrics.daily_usage_counts).map(([date, count]) => ({ date, count, })).sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()), weekly: Object.entries(metrics.time_metrics.weekly_usage_counts).map(([week, count]) => ({ week, count, })).sort((a, b) => a.week.localeCompare(b.week)), monthly: Object.entries(metrics.time_metrics.monthly_usage_counts).map(([month, count]) => ({ month, count, })).sort((a, b) => a.month.localeCompare(b.month)) } // Format for time of day patterns chart const timeOfDayData = metrics.time_metrics.time_of_day_patterns.map((count, index) => ({ hour: index, count, formattedHour: `${index}:00` })) // Bin specialization data for waste types const binSpecializationData = Object.entries(metrics.bin_specialization).map(([binId, data]) => { const bin = bins?.find(b => b.id === binId) || { name: binId, color: "#666666", color_dark: "#999999" } return { bin: bin.name, binId, mostCommonType: data.most_common_type, totalItems: data.total_items, fill: settings?.theme === 'dark' ? bin.color_dark : bin.color, ...Object.entries(data.items_by_type).reduce((acc, [type, count]) => { acc[type] = count return acc }, {} as Record<string, number>) } }) // Create data for most common waste types const allWasteTypes = Object.values(metrics.bin_specialization).flatMap( bin => Object.entries(bin.items_by_type).map(([type, count]) => ({ type, count })) ) const wasteTypeCounts: Record<string, number> = {} allWasteTypes.forEach(item => { wasteTypeCounts[item.type] = (wasteTypeCounts[item.type] || 0) + item.count }) const wasteTypeData = Object.entries(wasteTypeCounts) .map(([type, count]) => ({ type, count })) .sort((a, b) => b.count - a.count) // Custom colors for charts const chartConfig = { itemsPerBin: { label: "Items per Bin", color: "hsl(var(--chart-1))" }, apiVsLocal: { label: "Classification Methods", color: "hsl(var(--chart-2))" }, timeUsage: { label: "Sorting Trends", color: "hsl(var(--chart-3))" }, wasteTypes: { label: "Waste Types", color: "hsl(var(--chart-4))" } } return ( <SidebarProvider> <div className="flex h-screen w-full"> <AppSidebar /> <div className="flex-1 overflow-auto"> <header className="flex shrink-0 items-center gap-2 border-b px-4 py-6"> <SidebarTrigger /> <Separator orientation="vertical" className="mx-2 h-4" /> <Breadcrumb> <BreadcrumbList> <BreadcrumbItem className="hidden sm:block"> <BreadcrumbLink href="/">{t('navigation.home')}</BreadcrumbLink> </BreadcrumbItem> <BreadcrumbSeparator className="hidden sm:block" /> <BreadcrumbItem> <BreadcrumbPage>{t('navigation.sorting')}</BreadcrumbPage> </BreadcrumbItem> </BreadcrumbList> </Breadcrumb> </header> <main className="container mx-auto p-4 space-y-6"> <div className="flex flex-col gap-2"> <h1 className="text-3xl font-bold tracking-tight">{t('sorting.title')}</h1> <p className="text-muted-foreground"> {t('sorting.description')} </p> </div> {/* Sorting Distribution Section */} <section className="space-y-4"> <h2 className="text-2xl font-semibold tracking-tight">{t('sorting.distribution.title')}</h2> <div className="grid gap-4 md:grid-cols-2"> {/* Items per bin type */} <Card> <CardHeader className="pb-2"> <CardTitle>{t('sorting.distribution.itemsPerBin')}</CardTitle> <CardDescription> {t('sorting.distribution.itemsPerBinDescription')} </CardDescription> </CardHeader> <CardContent> <ChartContainer config={chartConfig} className="h-64 w-full"> <BarChart accessibilityLayer data={binItemsData} layout="vertical" margin={{ top: 5, right: 30, left: 50, bottom: 5 }} > <CartesianGrid horizontal={false} /> <XAxis type="number" /> <YAxis dataKey="bin" type="category" width={80} /> <ChartTooltip content={<ChartTooltipContent />} /> <ChartLegend content={<ChartLegendContent />} /> <Bar dataKey="count" name="Items Sorted" radius={4}> {binItemsData.map((entry, index) => ( <Cell key={`cell-${index}`} fill={entry.fill || `var(--chart-${(index % 5) + 1})`} /> ))} </Bar> </BarChart> </ChartContainer> </CardContent> </Card> {/* Classification methods (API vs. Local) */} <Card> <CardHeader className="pb-2"> <CardTitle>{t('sorting.distribution.classificationMethods')}</CardTitle> <CardDescription> {t('sorting.distribution.classificationMethodsDescription')} </CardDescription> </CardHeader> <CardContent> <ChartContainer config={chartConfig} className="h-64 w-full"> <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="name" />} /> <Pie data={apiVsLocalData} dataKey="value" nameKey="name" cx="50%" cy="50%" outerRadius={80} label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`} labelLine={false} > {apiVsLocalData.map((entry, index) => ( <Cell key={`cell-${index}`} fill={entry.fill || `var(--chart-${(index % 5) + 1})`} /> ))} </Pie> <ChartLegend content={<ChartLegendContent nameKey="name" />} /> </PieChart> </ChartContainer> </CardContent> </Card> </div> </section> {/* Trends Section */} <section className="space-y-4"> <h2 className="text-2xl font-semibold tracking-tight">{t('sorting.trends.title')}</h2> <Card> <CardHeader className="pb-2"> <CardTitle>{t('sorting.trends.sortingTrends')}</CardTitle> <CardDescription> {t('sorting.trends.sortingTrendsDescription')} </CardDescription> <Tabs defaultValue="daily" value={selectedTimeRange} onValueChange={setSelectedTimeRange} className="mt-2" > <TabsList> <TabsTrigger value="daily">{t('common.daily')}</TabsTrigger> <TabsTrigger value="weekly">{t('common.weekly')}</TabsTrigger> <TabsTrigger value="monthly">{t('common.monthly')}</TabsTrigger> </TabsList> </Tabs> </CardHeader> <CardContent> <ChartContainer config={chartConfig} className="h-64 w-full"> <LineChart accessibilityLayer data={timeSeriesData[selectedTimeRange as keyof typeof timeSeriesData]} margin={{ top: 5, right: 30, left: 20, bottom: 5 }} > <CartesianGrid vertical={false} strokeDasharray="3 3" /> <XAxis dataKey={selectedTimeRange === 'daily' ? 'date' : (selectedTimeRange === 'weekly' ? 'week' : 'month')} tickLine={false} tickMargin={10} tick={{ fontSize: 12 }} /> <YAxis /> <ChartTooltip content={<ChartTooltipContent />} /> <Line type="monotone" dataKey="count" stroke="var(--chart-3)" strokeWidth={2} dot={{ fill: 'var(--chart-3)', r: 4 }} activeDot={{ r: 6 }} /> </LineChart> </ChartContainer> </CardContent> </Card> <Card> <CardHeader className="pb-2"> <CardTitle>{t('sorting.trends.timeOfDay')}</CardTitle> <CardDescription> {t('sorting.trends.timeOfDayDescription')} </CardDescription> </CardHeader> <CardContent> <ChartContainer config={chartConfig} className="h-64 w-full"> <BarChart accessibilityLayer data={timeOfDayData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }} > <CartesianGrid vertical={false} strokeDasharray="3 3" /> <XAxis dataKey="formattedHour" tickLine={false} tickMargin={10} tick={{ fontSize: 10 }} interval={2} /> <YAxis /> <ChartTooltip content={<ChartTooltipContent />} /> <Bar dataKey="count" name="Items Sorted" radius={4} fill="var(--chart-2)" /> </BarChart> </ChartContainer> </CardContent> </Card> </section> {/* Top Classifications Section */} <section className="space-y-4"> <h2 className="text-2xl font-semibold tracking-tight">{t('sorting.topClassifications.title')}</h2> <Card> <CardHeader className="pb-2"> <CardTitle>{t('sorting.topClassifications.wasteTypes')}</CardTitle> <CardDescription> {t('sorting.topClassifications.wasteTypesDescription')} </CardDescription> </CardHeader> <CardContent> <div className="grid md:grid-cols-2 gap-6"> <div> <ChartContainer config={chartConfig} className="h-64 w-full"> <PieChart> <ChartTooltip content={<ChartTooltipContent nameKey="type" />} /> <Pie data={wasteTypeData} dataKey="count" nameKey="type" cx="50%" cy="50%" outerRadius={80} label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%` } > {wasteTypeData.map((entry, index) => ( <Cell key={`cell-${index}`} fill={`var(--chart-${(index % 5) + 1})`} /> ))} </Pie> <ChartLegend content={<ChartLegendContent nameKey="type" />} /> </PieChart> </ChartContainer> </div> <div className="overflow-auto"> <table className="w-full"> <thead> <tr className="border-b"> <th className="py-2 px-4 text-left">Waste Type</th> <th className="py-2 px-4 text-right">Count</th> <th className="py-2 px-4 text-right">%</th> </tr> </thead> <tbody> {wasteTypeData.map((item, index) => ( <tr key={index} className="border-b border-muted"> <td className="py-2 px-4">{item.type}</td> <td className="py-2 px-4 text-right">{item.count}</td> <td className="py-2 px-4 text-right"> {((item.count / metrics.basic_metrics.total_items_sorted) * 100).toFixed(1)}% </td> </tr> ))} </tbody> </table> </div> </div> </CardContent> </Card> </section> {/* Interactive Features Section */} <section className="space-y-4"> <h2 className="text-2xl font-semibold tracking-tight">{t('sorting.interactiveFeatures.title')}</h2> <div className="grid gap-4 md:grid-cols-2"> {/* Bin Fill Levels */} <Card> <CardHeader> <CardTitle>{t('sorting.interactiveFeatures.binFillLevels')}</CardTitle> </CardHeader> <CardContent> <div className="space-y-4"> {predictions.map((prediction) => { const bin = bins?.find(b => b.id === prediction.binId) || { name: prediction.binId, color: "#666666", color_dark: "#999999" } const fillColor = settings?.theme === 'dark' ? bin.color_dark : bin.color return ( <div key={prediction.binId} className="space-y-1"> <div className="flex justify-between"> <span>{bin.name}</span> <span>{prediction.currentLevel.toFixed(1)}%</span> </div> <div className="h-3 w-full bg-muted rounded-full overflow-hidden"> <div className="h-full rounded-full" style={{ width: `${prediction.currentLevel}%`, backgroundColor: fillColor }} /> </div> <div className="text-xs text-muted-foreground"> {prediction.timeUntilFull > 0 ? `Expected to fill in ${prediction.timeUntilFull.toFixed(1)} hours` : 'Full' } </div> </div> ) })} </div> </CardContent> </Card> {/* Bin emptying patterns */} <Card> <CardHeader> <CardTitle>{t('sorting.interactiveFeatures.binEmptying')}</CardTitle> </CardHeader> <CardContent> <div className="grid grid-cols-2 gap-4"> {Object.entries(metrics.basic_metrics.bin_emptying_counts) .map(([binId, count]) => { const bin = bins?.find(b => b.id === binId) || { name: binId, color: "#666666", color_dark: "#999999" } return ( <div key={binId} className="flex flex-col items-center justify-center p-4 border rounded-lg"> <div className="w-12 h-12 rounded-full mb-2 flex items-center justify-center text-white" style={{ backgroundColor: settings?.theme === 'dark' ? bin.color_dark : bin.color }} > {count} </div> <span className="text-sm font-medium">{bin.name}</span> <span className="text-xs text-muted-foreground">Emptied</span> </div> ) })} </div> </CardContent> </Card> </div> </section> </main> </div> </div> </SidebarProvider> ) }
Editor is loading...
Leave a Comment