Untitled
unknown
plain_text
9 months ago
7.9 kB
8
Indexable
// components/overview-cards.tsx
"use client"
import { useMetrics } from "@/hooks/useMetrics"
import { useBinConfig } from "@/hooks/use-bin-config"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Gauge, Clock, Trash2 } from "lucide-react"
import { format, differenceInHours, differenceInDays, subDays } from "date-fns"
export function OverviewCards() {
const { metrics } = useMetrics()
const { bins } = useBinConfig()
const formatTimeRemaining = (hours: number) => {
if (hours >= 24) {
const days = Math.floor(hours / 24)
const remainingHours = hours % 24
let result = `${days} Tag${days !== 1 ? 'e' : ''}`
if (remainingHours > 0) {
result += ` ${remainingHours} Stunde${remainingHours !== 1 ? 'n' : ''}`
}
return result
}
return `${hours} Stunde${hours !== 1 ? 'n' : ''}`
}
const getTimeColor = (hours: number) => {
if (hours <= 24) return 'bg-red-500/20 border-red-500'
if (hours <= 48) return 'bg-orange-500/20 border-orange-500'
return 'bg-green-500/20 border-green-500'
}
const getBinDisplay = (binId: string) => {
const binNumber = binId.replace('bin', '')
const configuredBin = bins.find(b => b.id === binId)
return {
name: configuredBin?.name || `Mülleimer ${binNumber}`,
fullLabel: `Mülleimer ${binNumber}`,
type: configuredBin?.name || 'Nicht konfiguriert'
}
}
// Card 1: Proactive emptying count
const proactiveEmptyings = metrics?.proactive_emptying?.count || 0
const totalEmptyings = metrics?.basic_metrics?.bin_emptying_counts
? Object.values(metrics.basic_metrics.bin_emptying_counts).reduce((a, b) => a + b, 0)
: 0
const proactivePercentage = totalEmptyings > 0
? Math.round((proactiveEmptyings / totalEmptyings) * 100)
: 0
// Card 2: Next full bin prediction
const predictions = metrics?.fill_predictions || {}
const nextFullBin = metrics?.fill_predictions
? Object.entries(metrics.fill_predictions).reduce((acc, [binId, prediction]) => {
const predictedTime = new Date(prediction.predicted_full_time)
if (!acc || predictedTime < acc.predictedTime) {
return {
binId,
predictedTime,
currentLevel: prediction.current_level,
...getBinDisplay(binId)
}
}
return acc
}, null as {
binId: string
predictedTime: Date
currentLevel: number
name: string
fullLabel: string
} | null)
: null
// Card 3: Average fill level at emptying
const fillLevels = metrics?.fill_level_history
? Object.values(metrics.fill_level_history).flatMap(h => h.fill_levels_at_empty)
: []
const averageFillLevel = fillLevels.length > 0
? Math.round(fillLevels.reduce((a, b) => a + b, 0) / fillLevels.length)
: null
// Card 4: Total emptyings count with weekly calculation
const totalEmptyingsLastWeek = metrics?.fill_level_history
? Object.values(metrics.fill_level_history).reduce((total, binHistory) => {
const lastWeek = subDays(new Date(), 7)
return binHistory.emptying_timestamps.filter(t =>
new Date(t) > lastWeek
).length + total
}, 0)
: 0
// Calculate time remaining and color for next full bin
let timeRemainingHours = 0
let timeColorClass = ''
let formattedTime = ''
if (nextFullBin) {
timeRemainingHours = Math.max(0, differenceInHours(nextFullBin.predictedTime, new Date()))
formattedTime = formatTimeRemaining(timeRemainingHours)
timeColorClass = getTimeColor(timeRemainingHours)
}
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
{/* Proactive Emptying Card */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Proaktive Leerungen</CardTitle>
<Trash2 className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{proactiveEmptyings}</div>
<p className="text-xs text-muted-foreground">
{totalEmptyings} Gesamtleerungen ({proactivePercentage}%)
</p>
</CardContent>
</Card>
{/* Next Full Bin Card */}
<Card className={nextFullBin ? `${timeColorClass} border-l-4` : ''}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Nächste volle Tonne</CardTitle>
<Clock className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
{nextFullBin ? (
<div className="space-y-3">
<div className="flex items-baseline gap-2">
<div className="text-2xl font-bold">
{nextFullBin.fullLabel}
</div>
<div className="text-lg text-muted-foreground">
({nextFullBin.type})
</div>
</div>
<div className="space-y-2">
<div className="text-sm text-muted-foreground">
Voraussichtlich voll:{" "}
<span className="font-medium">
{format(nextFullBin.predictedTime, "dd.MM.yyyy HH:mm")}
</span>
</div>
<div className="flex items-center gap-3">
<div className={`px-3 py-1 rounded-full ${timeColorClass} text-sm font-medium`}>
{formattedTime}
</div>
<div className="flex-1">
<div className="text-xs text-muted-foreground mb-1">
Aktueller Füllstand: {nextFullBin.currentLevel}%
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-500 rounded-full h-2"
style={{ width: `${nextFullBin.currentLevel}%` }}
/>
</div>
</div>
</div>
</div>
</div>
) : (
<div className="text-sm text-muted-foreground">
Alle Tonnen sind aktuell unterhalb der Kapazität
</div>
)}
</CardContent>
</Card>
{/* Average Fill Level Card */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Durchschn. Füllstand</CardTitle>
<Gauge className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{averageFillLevel !== null ? `${averageFillLevel}%` : "N/A"}
</div>
<p className="text-xs text-muted-foreground">
Bei Leerungen
</p>
</CardContent>
</Card>
{/* Total Emptyings Card */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Leerungen gesamt</CardTitle>
<Trash2 className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalEmptyings}</div>
<p className="text-xs text-muted-foreground">
+{totalEmptyingsLastWeek} letzte Woche
</p>
</CardContent>
</Card>
</div>
)
}Editor is loading...
Leave a Comment