Untitled

 avatar
unknown
plain_text
2 months ago
9.0 kB
3
Indexable
"use client"

import { useState, useEffect, useMemo } from "react"
import { format, parseISO, subDays, differenceInDays } from "date-fns"
import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Scatter } from "recharts"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { ChartLegend, ChartLegendContent, ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@/components/ui/chart"
import { useMetrics } from "@/hooks/useMetrics"
import { Calendar } from "@/components/ui/calendar"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { Button } from "@/components/ui/button"
import { CalendarIcon, X } from "lucide-react"
import { useSearchParams, useRouter } from "next/navigation"

const timeRanges = [
  { label: "All Time", value: 9999999 },
  { label: "Last 24h", value: 1 },
  { label: "Last Week", value: 7 },
  { label: "Last Month", value: 30 },
  { label: "Last Year", value: 365 },
  { label: "Custom", value: "custom" },
]

const binColors = {
  bin1: "#2563eb",
  bin2: "#059669",
  bin3: "#7c3aed",
  bin4: "#db2777",
}

export function HistoricalFillChart() {
  const router = useRouter()
  const [isCalendarOpen, setIsCalendarOpen] = useState(false)
  const searchParams = useSearchParams()
  const [selectedBins, setSelectedBins] = useState<string>("all")
  const [selectedTimeRange, setSelectedTimeRange] = useState<number | "custom">(30)
  const [customDateRange, setCustomDateRange] = useState<{ from: Date | undefined; to: Date | undefined }>({
    from: undefined,
    to: undefined,
  })
  const { metrics, helpers, loading, error } = useMetrics()

  const activeBinIds = useMemo(() => {
    if (!helpers) return []
    return helpers.getActiveBinIds()
  }, [helpers])

  useEffect(() => {
    const binFromUrl = searchParams.get("bin")
    if (binFromUrl && activeBinIds.includes(binFromUrl)) {
      setSelectedBins(binFromUrl)
    } else {
      setSelectedBins("all")
    }
  }, [searchParams, activeBinIds])

  const { chartData, emptyingEvents } = useMemo(() => {
    if (!helpers) return { chartData: [], emptyingEvents: [] }

    let startDate: Date
    let endDate = new Date()

    if (selectedTimeRange === "custom" && customDateRange.from && customDateRange.to) {
      startDate = customDateRange.from
      endDate = customDateRange.to
    } else if (typeof selectedTimeRange === "number") {
      startDate = subDays(endDate, selectedTimeRange)
    } else {
      startDate = subDays(endDate, 30)
    }

    const historicalData = helpers.getFillLevelHistory(undefined, { start: startDate, end: endDate })
    const emptyingData = helpers.getEmptyingEvents(undefined, { start: startDate, end: endDate })

    // Create merged timeline with all timestamps
    const allTimestamps = Array.from(
      new Set([
        ...historicalData.map(d => parseISO(d.timestamp).getTime()),
        ...emptyingData.map(d => parseISO(d.timestamp).getTime())
      ])
    ).sort((a, b) => a - b)

    // Create data structure with all bins at each timestamp
    const dataMap = allTimestamps.reduce((acc, timestamp) => {
      acc[timestamp] = {
        date: timestamp,
        ...activeBinIds.reduce((binAcc, binId) => ({
          ...binAcc,
          [binId]: null
        }), {})
      }
      return acc
    }, {} as Record<number, any>)

    // Fill in historical data
    historicalData.forEach(item => {
      const timestamp = parseISO(item.timestamp).getTime()
      if (dataMap[timestamp]) {
        dataMap[timestamp][item.bin_id] = item.fill_level
      }
    })

    return {
      chartData: Object.values(dataMap),
      emptyingEvents: emptyingData.map(event => ({
        date: parseISO(event.timestamp).getTime(),
        bin_id: event.bin_id,
        fill_level: event.fill_level,
      }))
    }
  }, [helpers, selectedTimeRange, customDateRange, activeBinIds])

  const chartConfig = useMemo<ChartConfig>(() => {
    const config: ChartConfig = {}
    const binsToShow = selectedBins === "all" ? activeBinIds : [selectedBins]

    binsToShow.forEach(binId => {
      config[binId] = {
        label: binId.toUpperCase(),
        color: binColors[binId as keyof typeof binColors],
      }
    })

    return config
  }, [selectedBins, activeBinIds])

  // ... keep handleBinChange and handleTimeRangeChange the same ...

  const getXAxisTickFormatter = () => {
    if (selectedTimeRange === 1) return (ts: number) => format(new Date(ts), "HH:mm")
    if (selectedTimeRange <= 7) return (ts: number) => format(new Date(ts), "MMM dd")
    if (selectedTimeRange <= 30) return (ts: number) => format(new Date(ts), "MMM dd")
    return (ts: number) => format(new Date(ts), "MMM yyyy")
  }

  const getXAxisInterval = () => {
    if (selectedTimeRange === 1) return 0
    if (selectedTimeRange <= 7) return 0
    if (selectedTimeRange <= 30) return 3
    return 7
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>Historical Fill Levels</CardTitle>
        <CardDescription>Fill level trends with emptying events</CardDescription>
      </CardHeader>
      <CardContent>
        {/* Keep UI controls same as before */}

        <ChartContainer config={chartConfig} className="aspect-auto h-[400px] w-full">
          <ResponsiveContainer width="100%" height="100%">
            <LineChart data={chartData}>
              <CartesianGrid vertical={false} />
              <XAxis
                dataKey="date"
                tickFormatter={getXAxisTickFormatter()}
                axisLine={false}
                tickLine={false}
                tickMargin={8}
                scale="time"
                type="number"
                domain={['dataMin', 'dataMax']}
                interval={getXAxisInterval()}
              />
              <YAxis
                domain={[0, 100]}
                axisLine={false}
                tickLine={false}
                tickFormatter={(value) => `${value}%`}
              />

              <ChartTooltip 
                content={({ active, payload }) => {
                  if (!active || !payload?.length) return null
                  
                  const timestamp = payload[0].payload.date
                  return (
                    <div className="rounded-lg border bg-background p-2 shadow-sm">
                      <div className="grid gap-2">
                        <div className="text-sm font-medium">
                          {format(new Date(timestamp), "MMM dd, yyyy HH:mm")}
                        </div>
                        {payload
                          .filter(entry => entry.dataKey !== 'date')
                          .map(entry => (
                            <div key={entry.dataKey} className="flex items-center gap-2">
                              <div 
                                className="h-2 w-2 rounded-full" 
                                style={{ background: entry.color }} 
                              />
                              <span className="text-sm text-muted-foreground">
                                {chartConfig[entry.dataKey]?.label}:
                              </span>
                              <span className="text-sm font-medium">
                                {entry.value?.toFixed(0) ?? '–'}%
                              </span>
                            </div>
                          ))}
                      </div>
                    </div>
                  )
                }} 
              />

              {Object.keys(chartConfig).map((binId) => (
                <Line
                  key={binId}
                  type="linear"
                  dataKey={binId}
                  stroke={chartConfig[binId].color}
                  strokeWidth={2}
                  dot={false}
                  connectNulls={false}
                />
              ))}

              <Scatter
                name="Emptying Events"
                data={emptyingEvents}
                fill="#ff7300"
                shape={(props) => {
                  const binColor = binColors[props.payload.bin_id as keyof typeof binColors]
                  return (
                    <circle
                      {...props}
                      cx={props.cx}
                      cy={props.cy}
                      r={6}
                      fill={binColor}
                      stroke="#fff"
                      strokeWidth={2}
                    />
                  )
                }}
              />
            </LineChart>
          </ResponsiveContainer>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
Editor is loading...
Leave a Comment