Untitled

 avatar
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