Untitled
unknown
plain_text
8 months ago
23 kB
5
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