add statistics
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { BarChart } from "@tremor/react";
|
||||
|
||||
interface AbortReasonsBarChartProps {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: temporary fix
|
||||
data: any[];
|
||||
}
|
||||
|
||||
export function AbortReasonsBarChart(props: AbortReasonsBarChartProps) {
|
||||
const { data } = props;
|
||||
|
||||
const dataFormatter = (number: number) => Intl.NumberFormat("de-DE").format(number).toString();
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
className="mt-6"
|
||||
data={data}
|
||||
index="name"
|
||||
categories={["Anzahl"]}
|
||||
colors={["blue"]}
|
||||
valueFormatter={dataFormatter}
|
||||
yAxisWidth={48}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { DonutChart, Legend } from "@tremor/react";
|
||||
|
||||
const dataFormatter = (number: number) => Intl.NumberFormat("de-DE").format(number).toString();
|
||||
|
||||
interface LoadFactorChartProps {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: temp. fix
|
||||
data: any[];
|
||||
}
|
||||
export function LoadFactorChart(props: LoadFactorChartProps) {
|
||||
const { data } = props;
|
||||
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
<DonutChart data={data} variant="donut" colors={["green", "yellow"]} valueFormatter={dataFormatter} />
|
||||
<Legend categories={["Frei", "Belegt"]} colors={["green", "yellow"]} className="max-w-xs" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import { TrendingUp } from "lucide-react";
|
||||
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from "recharts";
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||
import type { PrinterErrorRate } from "@/utils/analytics/error-rate";
|
||||
|
||||
export const description = "Ein Säulendiagramm zur Darstellung der Fehlerrate";
|
||||
|
||||
interface PrinterErrorRateChartProps {
|
||||
printerErrorRate: PrinterErrorRate[];
|
||||
}
|
||||
|
||||
const chartConfig = {
|
||||
errorRate: {
|
||||
label: "Fehlerrate",
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
export function PrinterErrorRateChart({ printerErrorRate }: PrinterErrorRateChartProps) {
|
||||
// Transform data to fit the chart structure
|
||||
const chartData = printerErrorRate.map((printer) => ({
|
||||
printer: printer.name,
|
||||
errorRate: printer.errorRate,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Fehlerrate</CardTitle>
|
||||
<CardDescription>Fehlerrate der Drucker in Prozent</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
top: 20,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
dataKey="printer"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value}
|
||||
/>
|
||||
<YAxis tickFormatter={(value) => `${value}%`} />
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Bar dataKey="errorRate" fill="hsl(var(--chart-1))" radius={8}>
|
||||
<LabelList
|
||||
position="top"
|
||||
offset={12}
|
||||
className="fill-foreground"
|
||||
fontSize={12}
|
||||
formatter={(value: number) => `${value}%`}
|
||||
/>
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Fehlerratenanalyse abgeschlossen <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">Zeigt die Fehlerrate für jeden Drucker</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
"use client";
|
||||
import { Bar, BarChart, XAxis, YAxis } from "recharts";
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||
import { StopwatchIcon } from "@radix-ui/react-icons";
|
||||
|
||||
export const description = "Ein horizontales Balkendiagramm zur Darstellung der durchschnittlichen Leerlaufzeit";
|
||||
|
||||
interface PrinterIdleTime {
|
||||
printerId: string;
|
||||
printerName: string;
|
||||
averageIdleTime: number; // in minutes
|
||||
}
|
||||
|
||||
interface PrinterIdleTimeChartProps {
|
||||
printerIdleTime: PrinterIdleTime[];
|
||||
}
|
||||
|
||||
const chartConfig = {
|
||||
idleTime: {
|
||||
label: "Leerlaufzeit",
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
export function PrinterIdleTimeChart({ printerIdleTime }: PrinterIdleTimeChartProps) {
|
||||
// Transform data to fit the chart structure
|
||||
const chartData = printerIdleTime.map((printer) => ({
|
||||
printer: printer.printerName,
|
||||
idleTime: printer.averageIdleTime,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Leerlaufzeit der Drucker</CardTitle>
|
||||
<CardDescription>Durchschnittliche Leerlaufzeit der Drucker in Minuten</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer className="h-64" config={chartConfig}>
|
||||
<BarChart accessibilityLayer data={chartData} layout="vertical">
|
||||
<XAxis type="number" dataKey="idleTime" hide />
|
||||
<YAxis
|
||||
dataKey="printer"
|
||||
type="category"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Bar dataKey="idleTime" fill="hsl(var(--chart-1))" radius={5} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Durchschnittliche Leerlaufzeiten der Drucker <StopwatchIcon className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Zeigt die durchschnittliche Leerlaufzeit für jeden Drucker
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import { TrendingUp } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { Label, Pie, PieChart } from "recharts";
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||
|
||||
export const description = "Nutzung des Druckers";
|
||||
|
||||
interface ComponentProps {
|
||||
data: {
|
||||
printerId: string;
|
||||
utilizationPercentage: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
const chartConfig = {} satisfies ChartConfig;
|
||||
|
||||
export function PrinterUtilizationChart({ data }: ComponentProps) {
|
||||
const totalUtilization = React.useMemo(() => data.utilizationPercentage, [data]);
|
||||
const dataWithColor = {
|
||||
...data,
|
||||
fill: "rgb(34 197 94)",
|
||||
};
|
||||
const free = {
|
||||
printerId: "-",
|
||||
utilizationPercentage: 1 - data.utilizationPercentage,
|
||||
name: "(Frei)",
|
||||
fill: "rgb(212 212 212)",
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>{data.name}</CardTitle>
|
||||
<CardDescription>Nutzung des ausgewählten Druckers</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<PieChart>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Pie
|
||||
data={[dataWithColor, free]}
|
||||
dataKey="utilizationPercentage"
|
||||
nameKey="name"
|
||||
innerRadius={60}
|
||||
strokeWidth={5}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
|
||||
return (
|
||||
<text x={viewBox.cx} y={viewBox.cy} textAnchor="middle" dominantBaseline="middle">
|
||||
<tspan x={viewBox.cx} y={viewBox.cy} className="fill-foreground text-3xl font-bold">
|
||||
{(totalUtilization * 100).toFixed(2)}%
|
||||
</tspan>
|
||||
<tspan x={viewBox.cx} y={(viewBox.cy || 0) + 24} className="fill-muted-foreground">
|
||||
Gesamt-Nutzung
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Übersicht der Nutzung <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">Aktuelle Auslastung des Druckers</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
"use client";
|
||||
import { Bar, BarChart, CartesianGrid, LabelList, XAxis } from "recharts";
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||
|
||||
export const description = "Ein Balkendiagramm mit Beschriftung";
|
||||
|
||||
interface PrintVolumes {
|
||||
today: number;
|
||||
thisWeek: number;
|
||||
thisMonth: number;
|
||||
}
|
||||
|
||||
const chartConfig = {
|
||||
volume: {
|
||||
label: "Volumen",
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
interface PrinterVolumeChartProps {
|
||||
printerVolume: PrintVolumes;
|
||||
}
|
||||
|
||||
export function PrinterVolumeChart({ printerVolume }: PrinterVolumeChartProps) {
|
||||
const chartData = [
|
||||
{ period: "Heute", volume: printerVolume.today, color: "hsl(var(--chart-1))" },
|
||||
{ period: "Diese Woche", volume: printerVolume.thisWeek, color: "hsl(var(--chart-2))" },
|
||||
{ period: "Diesen Monat", volume: printerVolume.thisMonth, color: "hsl(var(--chart-3))" },
|
||||
];
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Druckvolumen</CardTitle>
|
||||
<CardDescription>Vergleich: Heute, Diese Woche, Diesen Monat</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer className="h-64" config={chartConfig}>
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
top: 20,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="period"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Bar dataKey="volume" fill="var(--color-volume)" radius={8}>
|
||||
<LabelList position="top" offset={12} className="fill-foreground" fontSize={12} />
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Zeigt das Druckvolumen für heute, diese Woche und diesen Monat
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { DonutChart, Legend } from "@tremor/react";
|
||||
|
||||
const dataFormatter = (number: number) => Intl.NumberFormat("de-DE").format(number).toString();
|
||||
|
||||
interface PrintJobsDonutProps {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: temp. fix
|
||||
data: any[];
|
||||
}
|
||||
export function PrintJobsDonut(props: PrintJobsDonutProps) {
|
||||
const { data } = props;
|
||||
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
<DonutChart data={data} variant="donut" colors={["green", "red", "yellow"]} valueFormatter={dataFormatter} />
|
||||
<Legend
|
||||
categories={["Abgeschlossen", "Abgebrochen", "Ausstehend"]}
|
||||
colors={["green", "red", "yellow"]}
|
||||
className="max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,15 @@
|
||||
import { PrinterErrorRateChart } from "@/app/admin/charts/printer-error";
|
||||
import { PrinterIdleTimeChart } from "@/app/admin/charts/printer-idle";
|
||||
import { PrinterUtilizationChart } from "@/app/admin/charts/printer-utilization";
|
||||
import { PrinterVolumeChart } from "@/app/admin/charts/printer-volume";
|
||||
import { DataCard } from "@/components/data-card";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { db } from "@/server/db";
|
||||
import { calculatePrinterErrorRate } from "@/utils/analytics/error-rate";
|
||||
import { calculatePrinterIdleTime } from "@/utils/analytics/idle-time";
|
||||
import { calculatePrinterUtilization } from "@/utils/analytics/utilization";
|
||||
import { calculatePrintVolumes } from "@/utils/analytics/volume";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -9,114 +19,75 @@ export const metadata: Metadata = {
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminPage() {
|
||||
/*const allPrintJobs = await db.query.printJobs.findMany({
|
||||
const currentDate = new Date();
|
||||
|
||||
const lastMonth = new Date();
|
||||
lastMonth.setDate(currentDate.getDate() - 31);
|
||||
const printers = await db.query.printers.findMany({});
|
||||
const printJobs = await db.query.printJobs.findMany({
|
||||
where: (job, { gte }) => gte(job.startAt, lastMonth),
|
||||
with: {
|
||||
printer: true,
|
||||
},
|
||||
});
|
||||
if (printJobs.length < 1) {
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle>Druckaufträge</CardTitle>
|
||||
<CardDescription>Zurzeit sind keine Druckaufträge verfügbar.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Aktualisieren Sie die Seite oder prüfen Sie später erneut, ob neue Druckaufträge verfügbar sind.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const totalAmountOfPrintJobs = allPrintJobs.length;
|
||||
|
||||
const now = new Date();
|
||||
const completedPrintJobs = allPrintJobs.filter((job) => {
|
||||
const currentPrintJobs = printJobs.filter((job) => {
|
||||
if (job.aborted) return false;
|
||||
|
||||
const endAt = new Date(job.startAt).getTime() + job.durationInMinutes * 1000 * 60;
|
||||
return endAt < now.getTime();
|
||||
}).length;
|
||||
const abortedPrintJobs = allPrintJobs.filter((job) => job.aborted).length;
|
||||
const pendingPrintJobs = totalAmountOfPrintJobs - completedPrintJobs - abortedPrintJobs;
|
||||
|
||||
const abortedPrintJobsReasons = Object.entries(
|
||||
totalAmountOfPrintJobs > 0
|
||||
? allPrintJobs.reduce((accumulator: Record<string, number>, job) => {
|
||||
if (job.aborted && job.abortReason) {
|
||||
if (!accumulator[job.abortReason]) {
|
||||
accumulator[job.abortReason] = 1;
|
||||
} else {
|
||||
accumulator[job.abortReason]++;
|
||||
}
|
||||
}
|
||||
return accumulator;
|
||||
}, {})
|
||||
: {},
|
||||
).map(([name, count]) => ({ name, Anzahl: count }));
|
||||
|
||||
const mostAbortedPrinter = totalAmountOfPrintJobs > 0 ? allPrintJobs.reduce((prev, current) => (prev.aborted > current.aborted ? prev : current));
|
||||
|
||||
const mostUsedPrinter = allPrintJobs.reduce((prev, current) =>
|
||||
prev.durationInMinutes > current.durationInMinutes ? prev : current,
|
||||
);
|
||||
|
||||
const allPrinters = await db.query.printers.findMany();
|
||||
|
||||
const freePrinters = allPrinters.filter((printer) => {
|
||||
const jobs = allPrintJobs.filter((job) => job.printerId === printer.id);
|
||||
const now = new Date();
|
||||
const inUse = jobs.some((job) => {
|
||||
const endAt = new Date(job.startAt).getTime() + job.durationInMinutes * 1000 * 60;
|
||||
return endAt > now.getTime();
|
||||
});
|
||||
return !inUse;
|
||||
});*/
|
||||
return endAt > currentDate.getTime();
|
||||
});
|
||||
const occupiedPrinters = currentPrintJobs.map((job) => job.printer.id);
|
||||
const freePrinters = printers.filter((printer) => !occupiedPrinters.includes(printer.id));
|
||||
const printerUtilization = calculatePrinterUtilization(printJobs);
|
||||
const printerVolume = calculatePrintVolumes(printJobs);
|
||||
const printerIdleTime = calculatePrinterIdleTime(printJobs, printers);
|
||||
const printerErrorRate = calculatePrinterErrorRate(printJobs);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs defaultValue={"@general"} className="flex flex-col gap-4 items-start">
|
||||
<TabsList className="bg-neutral-100 w-full py-6">
|
||||
<TabsTrigger value="@general">Allgemein</TabsTrigger>
|
||||
{/*allPrinters.map((printer) => (
|
||||
<TabsTrigger key={printer.id} value={printer.id}>
|
||||
{printer.name}
|
||||
</TabsTrigger>
|
||||
))*/}
|
||||
<TabsTrigger value="@capacity">Druckerauslastung</TabsTrigger>
|
||||
<TabsTrigger value="@report">Statistiken & Berichte</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="@general" className="w-full">
|
||||
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-4">
|
||||
{/*<DataCard title="Drucker mit meisten Reservierungen" value={mostUsedPrinter.printer.name} icon="Printer" />
|
||||
<DataCard title="Drucker mit meisten Abbrüchen" value={mostAbortedPrinter.printer.name} icon="Printer" />*/}
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle>Druckaufträge</CardTitle>
|
||||
<CardDescription>nach Status</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/*<PrintJobsDonut
|
||||
data={[
|
||||
{ name: "Abgeschlossen", value: completedPrintJobs },
|
||||
{ name: "Abgebrochen", value: abortedPrintJobs },
|
||||
{ name: "Ausstehend", value: pendingPrintJobs },
|
||||
]}
|
||||
/>*/}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="w-full ">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
{/* Auslastung: <span>{((1 - freePrinters.length / allPrinters.length) * 100).toFixed(2)}%</span> */}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/*<LoadFactorChart
|
||||
data={[
|
||||
{ name: "Frei", value: freePrinters.length },
|
||||
{ name: "Belegt", value: allPrinters.length - freePrinters.length },
|
||||
]}
|
||||
/>*/}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="w-full col-span-2">
|
||||
<CardHeader>
|
||||
<CardTitle>Abgebrochene Druckaufträge nach Abbruchgrund</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>{/*<AbortReasonsBarChart data={abortedPrintJobsReasons} />*/}</CardContent>
|
||||
</Card>
|
||||
<DataCard title="Aktive Drucker" value={occupiedPrinters.length} icon={"Rotate3d"} />
|
||||
<DataCard title="Freie Drucker" value={freePrinters.length} icon={"PowerOff"} />
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="@capacity" className="w-full">
|
||||
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-4">
|
||||
{printerUtilization.map((data) => (
|
||||
<PrinterUtilizationChart key={data.printerId} data={data} />
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="@report" className="w-full">
|
||||
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-4">
|
||||
<PrinterIdleTimeChart printerIdleTime={printerIdleTime} />
|
||||
<PrinterVolumeChart printerVolume={printerVolume} />
|
||||
<div className="w-full col-span-2">
|
||||
<PrinterErrorRateChart printerErrorRate={printerErrorRate} />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
{/*allPrinters.map((printer) => (
|
||||
<TabsContent key={printer.id} value={printer.id}>
|
||||
{printer.description}
|
||||
</TabsContent>
|
||||
))*/}
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user