adjust statistics
This commit is contained in:
parent
fcb6d135ee
commit
dd7246f525
@ -0,0 +1,68 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||||
|
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from "recharts";
|
||||||
|
|
||||||
|
export const description = "Ein Säulendiagramm zur Darstellung der Abbruchgründe und ihrer Häufigkeit";
|
||||||
|
|
||||||
|
interface AbortReasonCountChartProps {
|
||||||
|
abortReasonCount: {
|
||||||
|
abortReason: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
abortReason: {
|
||||||
|
label: "Abbruchgrund",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig;
|
||||||
|
|
||||||
|
export function AbortReasonCountChart({ abortReasonCount }: AbortReasonCountChartProps) {
|
||||||
|
// Transform data to fit the chart structure
|
||||||
|
const chartData = abortReasonCount.map((reason) => ({
|
||||||
|
abortReason: reason.abortReason,
|
||||||
|
count: reason.count,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Abbruchgründe</CardTitle>
|
||||||
|
<CardDescription>Häufigkeit der Abbruchgründe für Druckaufträge</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ChartContainer config={chartConfig}>
|
||||||
|
<BarChart
|
||||||
|
accessibilityLayer
|
||||||
|
data={chartData}
|
||||||
|
margin={{
|
||||||
|
top: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||||
|
<XAxis
|
||||||
|
dataKey="abortReason"
|
||||||
|
tickLine={false}
|
||||||
|
tickMargin={10}
|
||||||
|
axisLine={false}
|
||||||
|
tickFormatter={(value) => value}
|
||||||
|
/>
|
||||||
|
<YAxis tickFormatter={(value) => `${value}`} />
|
||||||
|
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||||
|
<Bar dataKey="count" 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>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
@ -1,9 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { TrendingUp } from "lucide-react";
|
|
||||||
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from "recharts";
|
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from "recharts";
|
||||||
|
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||||
import type { PrinterErrorRate } from "@/utils/analytics/error-rate";
|
import type { PrinterErrorRate } from "@/utils/analytics/error-rate";
|
||||||
|
|
||||||
@ -63,12 +61,6 @@ export function PrinterErrorRateChart({ printerErrorRate }: PrinterErrorRateChar
|
|||||||
</BarChart>
|
</BarChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
</CardContent>
|
</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>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||||
|
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
|
||||||
|
|
||||||
|
export const description = "Ein Bereichsdiagramm zur Darstellung der prognostizierten Nutzung pro Wochentag";
|
||||||
|
|
||||||
|
interface ForecastData {
|
||||||
|
day: number; // 0 for Sunday, 1 for Monday, ..., 6 for Saturday
|
||||||
|
usageMinutes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForecastChartProps {
|
||||||
|
forecastData: ForecastData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
usage: {
|
||||||
|
label: "Prognostizierte Nutzung",
|
||||||
|
color: "hsl(var(--chart-1))",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig;
|
||||||
|
|
||||||
|
const daysOfWeek = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];
|
||||||
|
|
||||||
|
export function ForecastPrinterUsageChart({ forecastData }: ForecastChartProps) {
|
||||||
|
// Transform and slice data to fit the chart structure
|
||||||
|
const chartData = forecastData.map((data) => ({
|
||||||
|
//slice(1, forecastData.length - 1).
|
||||||
|
day: daysOfWeek[data.day], // Map day number to weekday name
|
||||||
|
usage: data.usageMinutes,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Prognostizierte Nutzung pro Wochentag</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ChartContainer className="h-64 w-full" config={chartConfig}>
|
||||||
|
<AreaChart accessibilityLayer data={chartData} margin={{ left: 12, right: 12, top: 12 }}>
|
||||||
|
<CartesianGrid vertical={true} />
|
||||||
|
<XAxis dataKey="day" type="category" tickLine={true} tickMargin={10} axisLine={false} />
|
||||||
|
<YAxis type="number" dataKey="usage" tickLine={false} tickMargin={10} axisLine={false} />
|
||||||
|
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||||
|
<Area
|
||||||
|
dataKey="usage"
|
||||||
|
type="step"
|
||||||
|
fill="hsl(var(--chart-1))"
|
||||||
|
fillOpacity={0.4}
|
||||||
|
stroke="hsl(var(--chart-1))"
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||||
|
<div className="flex items-center gap-2 font-medium leading-none">
|
||||||
|
Zeigt die prognostizierte Nutzungszeit pro Wochentag in Minuten.
|
||||||
|
</div>
|
||||||
|
<div className="leading-none text-muted-foreground">
|
||||||
|
Besten Tage zur Wartung: {bestMaintenanceDays(forecastData)}
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bestMaintenanceDays(forecastData: ForecastData[]) {
|
||||||
|
const sortedData = forecastData.map((a) => a).sort((a, b) => a.usageMinutes - b.usageMinutes); // Sort ascending
|
||||||
|
|
||||||
|
const q1Index = Math.floor(sortedData.length * 0.33);
|
||||||
|
const q1 = sortedData[q1Index].usageMinutes; // First quartile (Q1) value
|
||||||
|
|
||||||
|
const filteredData = sortedData.filter((data) => data.usageMinutes <= q1);
|
||||||
|
|
||||||
|
return filteredData
|
||||||
|
.map((data) => {
|
||||||
|
const days = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];
|
||||||
|
return days[data.day];
|
||||||
|
})
|
||||||
|
.join(", ");
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import { Bar, BarChart, CartesianGrid, 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";
|
|
||||||
|
|
||||||
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 w-full" config={chartConfig}>
|
|
||||||
<BarChart accessibilityLayer data={chartData} layout="vertical">
|
|
||||||
<CartesianGrid vertical={true} />
|
|
||||||
<XAxis type="number" dataKey="idleTime" label={"Minuten"} />
|
|
||||||
<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="leading-none text-muted-foreground">
|
|
||||||
Zeigt die durchschnittliche Leerlaufzeit für jeden Drucker in Minuten
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
import { PrinterErrorRateChart } from "@/app/admin/charts/printer-error";
|
import { AbortReasonCountChart } from "@/app/admin/charts/printer-error-chart";
|
||||||
import { PrinterIdleTimeChart } from "@/app/admin/charts/printer-idle";
|
import { PrinterErrorRateChart } from "@/app/admin/charts/printer-error-rate";
|
||||||
|
import { ForecastPrinterUsageChart } from "@/app/admin/charts/printer-forecast";
|
||||||
import { PrinterUtilizationChart } from "@/app/admin/charts/printer-utilization";
|
import { PrinterUtilizationChart } from "@/app/admin/charts/printer-utilization";
|
||||||
import { PrinterVolumeChart } from "@/app/admin/charts/printer-volume";
|
import { PrinterVolumeChart } from "@/app/admin/charts/printer-volume";
|
||||||
import { DataCard } from "@/components/data-card";
|
import { DataCard } from "@/components/data-card";
|
||||||
@ -7,7 +8,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import { calculatePrinterErrorRate } from "@/utils/analytics/error-rate";
|
import { calculatePrinterErrorRate } from "@/utils/analytics/error-rate";
|
||||||
import { calculatePrinterIdleTime } from "@/utils/analytics/idle-time";
|
import { calculateAbortReasonsCount } from "@/utils/analytics/errors";
|
||||||
|
import { forecastPrinterUsage } from "@/utils/analytics/forecast";
|
||||||
import { calculatePrinterUtilization } from "@/utils/analytics/utilization";
|
import { calculatePrinterUtilization } from "@/utils/analytics/utilization";
|
||||||
import { calculatePrintVolumes } from "@/utils/analytics/volume";
|
import { calculatePrintVolumes } from "@/utils/analytics/volume";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
@ -55,8 +57,9 @@ export default async function AdminPage() {
|
|||||||
const freePrinters = printers.filter((printer) => !occupiedPrinters.includes(printer.id));
|
const freePrinters = printers.filter((printer) => !occupiedPrinters.includes(printer.id));
|
||||||
const printerUtilization = calculatePrinterUtilization(printJobs);
|
const printerUtilization = calculatePrinterUtilization(printJobs);
|
||||||
const printerVolume = calculatePrintVolumes(printJobs);
|
const printerVolume = calculatePrintVolumes(printJobs);
|
||||||
const printerIdleTime = calculatePrinterIdleTime(printJobs, printers);
|
const printerAbortReasons = calculateAbortReasonsCount(printJobs);
|
||||||
const printerErrorRate = calculatePrinterErrorRate(printJobs);
|
const printerErrorRate = calculatePrinterErrorRate(printJobs);
|
||||||
|
const printerForecast = forecastPrinterUsage(printJobs);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -64,16 +67,27 @@ export default async function AdminPage() {
|
|||||||
<TabsList className="bg-neutral-100 w-full py-6">
|
<TabsList className="bg-neutral-100 w-full py-6">
|
||||||
<TabsTrigger value="@general">Allgemein</TabsTrigger>
|
<TabsTrigger value="@general">Allgemein</TabsTrigger>
|
||||||
<TabsTrigger value="@capacity">Druckerauslastung</TabsTrigger>
|
<TabsTrigger value="@capacity">Druckerauslastung</TabsTrigger>
|
||||||
<TabsTrigger value="@report">Statistiken & Berichte</TabsTrigger>
|
<TabsTrigger value="@report">Fehlerberichte</TabsTrigger>
|
||||||
|
<TabsTrigger value="@forecasts">Prognosen</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="@general" className="w-full">
|
<TabsContent value="@general" className="w-full">
|
||||||
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-4">
|
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-4">
|
||||||
|
<div className="w-full col-span-2">
|
||||||
|
<DataCard
|
||||||
|
title="Aktuelle Auslastung"
|
||||||
|
value={`${Math.round((occupiedPrinters.length / (freePrinters.length + occupiedPrinters.length)) * 100)}%`}
|
||||||
|
icon={"Percent"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<DataCard title="Aktive Drucker" value={occupiedPrinters.length} icon={"Rotate3d"} />
|
<DataCard title="Aktive Drucker" value={occupiedPrinters.length} icon={"Rotate3d"} />
|
||||||
<DataCard title="Freie Drucker" value={freePrinters.length} icon={"PowerOff"} />
|
<DataCard title="Freie Drucker" value={freePrinters.length} icon={"PowerOff"} />
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="@capacity" className="w-full">
|
<TabsContent value="@capacity" className="w-full">
|
||||||
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-4">
|
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-4">
|
||||||
|
<div className="w-full col-span-2">
|
||||||
|
<PrinterVolumeChart printerVolume={printerVolume} />
|
||||||
|
</div>
|
||||||
{printerUtilization.map((data) => (
|
{printerUtilization.map((data) => (
|
||||||
<PrinterUtilizationChart key={data.printerId} data={data} />
|
<PrinterUtilizationChart key={data.printerId} data={data} />
|
||||||
))}
|
))}
|
||||||
@ -81,11 +95,24 @@ export default async function AdminPage() {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="@report" className="w-full">
|
<TabsContent value="@report" className="w-full">
|
||||||
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-4">
|
<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">
|
<div className="w-full col-span-2">
|
||||||
<PrinterErrorRateChart printerErrorRate={printerErrorRate} />
|
<PrinterErrorRateChart printerErrorRate={printerErrorRate} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="w-full col-span-2">
|
||||||
|
<AbortReasonCountChart abortReasonCount={printerAbortReasons} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="@forecasts" className="w-full">
|
||||||
|
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-4">
|
||||||
|
<div className="w-full col-span-2">
|
||||||
|
<ForecastPrinterUsageChart
|
||||||
|
forecastData={printerForecast.map((usageMinutes, index) => ({
|
||||||
|
day: index,
|
||||||
|
usageMinutes,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
39
packages/reservation-platform/src/utils/analytics/errors.ts
Normal file
39
packages/reservation-platform/src/utils/analytics/errors.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import type { InferResultType } from "@/utils/drizzle";
|
||||||
|
|
||||||
|
export interface AbortReasonCount {
|
||||||
|
abortReason: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the count of each unique abort reason for print jobs.
|
||||||
|
*
|
||||||
|
* @param pJobs - Array of print job objects.
|
||||||
|
* @returns An array of AbortReasonCount objects, each containing the abort reason and its count.
|
||||||
|
*/
|
||||||
|
export function calculateAbortReasonsCount(pJobs: InferResultType<"printJobs">[]): AbortReasonCount[] {
|
||||||
|
if (pJobs.length === 0) {
|
||||||
|
return []; // No jobs, no data.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter aborted jobs and count each abort reason
|
||||||
|
const abortReasonsCount = pJobs
|
||||||
|
.filter((job) => job.aborted && job.abortReason) // Consider only aborted jobs with a reason
|
||||||
|
.reduce(
|
||||||
|
(acc, job) => {
|
||||||
|
const reason = job.abortReason || "Unbekannter Grund";
|
||||||
|
if (!acc[reason]) {
|
||||||
|
acc[reason] = 0;
|
||||||
|
}
|
||||||
|
acc[reason]++;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, number>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert the result to an array of AbortReasonCount objects
|
||||||
|
return Object.entries(abortReasonsCount).map(([abortReason, count]) => ({
|
||||||
|
abortReason,
|
||||||
|
count,
|
||||||
|
}));
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
import type { InferResultType } from "@/utils/drizzle";
|
||||||
|
|
||||||
|
type UsagePerDay = {
|
||||||
|
day: number; // 0 (Sunday) to 6 (Saturday)
|
||||||
|
usageMinutes: number;
|
||||||
|
dataPoints: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function aggregateUsageByDay(jobs: InferResultType<"printJobs">[]): UsagePerDay[] {
|
||||||
|
const usagePerDayMap = new Map<number, number>();
|
||||||
|
const usagePerDayDatapointsMap = new Map<number, number>();
|
||||||
|
|
||||||
|
for (const job of jobs) {
|
||||||
|
let remainingDuration = job.durationInMinutes;
|
||||||
|
const currentStart = new Date(job.startAt);
|
||||||
|
|
||||||
|
while (remainingDuration > 0) {
|
||||||
|
const day = currentStart.getDay();
|
||||||
|
const dataPoints = usagePerDayDatapointsMap.get(day) || 0;
|
||||||
|
usagePerDayDatapointsMap.set(day, dataPoints + 1);
|
||||||
|
|
||||||
|
// Calculate minutes remaining in the current day
|
||||||
|
const minutesRemainingInDay = (24 - currentStart.getHours()) * 60 - currentStart.getMinutes();
|
||||||
|
const minutesToAdd = Math.min(remainingDuration, minutesRemainingInDay);
|
||||||
|
|
||||||
|
// Update the usage for the current day
|
||||||
|
const usageMinutes = usagePerDayMap.get(day) || 0;
|
||||||
|
usagePerDayMap.set(day, usageMinutes + minutesToAdd);
|
||||||
|
|
||||||
|
// Update remaining duration and move to the next day
|
||||||
|
remainingDuration -= minutesToAdd;
|
||||||
|
currentStart.setDate(currentStart.getDate() + 1);
|
||||||
|
currentStart.setHours(0, 0, 0, 0); // Start at the beginning of the next day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const usageData: UsagePerDay[] = Array.from({ length: 7 }, (_, day) => ({
|
||||||
|
day,
|
||||||
|
usageMinutes: usagePerDayMap.get(day) || 0,
|
||||||
|
dataPoints: usagePerDayDatapointsMap.get(day) || 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return usageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function forecastPrinterUsage(jobs: InferResultType<"printJobs">[]): number[] {
|
||||||
|
const usageData = aggregateUsageByDay(jobs);
|
||||||
|
console.log(usageData);
|
||||||
|
const forecasts: number[] = [];
|
||||||
|
for (const data of usageData) {
|
||||||
|
let usagePrediction = data.usageMinutes / data.dataPoints;
|
||||||
|
if (Number.isNaN(usagePrediction)) {
|
||||||
|
usagePrediction = 0;
|
||||||
|
}
|
||||||
|
forecasts.push(Math.round(usagePrediction));
|
||||||
|
}
|
||||||
|
|
||||||
|
return forecasts;
|
||||||
|
}
|
@ -1,49 +0,0 @@
|
|||||||
import type { printJobs, printers } from "@/server/db/schema";
|
|
||||||
import { endOfMonth, startOfMonth } from "date-fns";
|
|
||||||
import type { InferSelectModel } from "drizzle-orm";
|
|
||||||
|
|
||||||
interface PrinterIdleTime {
|
|
||||||
printerId: string;
|
|
||||||
printerName: string;
|
|
||||||
averageIdleTime: number; // in minutes
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the average idle time for each printer within the current month.
|
|
||||||
*
|
|
||||||
* @param printJobs - Array of print job objects.
|
|
||||||
* @param printers - Array of printer objects.
|
|
||||||
* @returns An array of PrinterIdleTime objects with average idle times.
|
|
||||||
*/
|
|
||||||
export function calculatePrinterIdleTime(
|
|
||||||
pJobs: InferSelectModel<typeof printJobs>[],
|
|
||||||
p: InferSelectModel<typeof printers>[],
|
|
||||||
): PrinterIdleTime[] {
|
|
||||||
const now = new Date();
|
|
||||||
const startOfThisMonth = startOfMonth(now);
|
|
||||||
const endOfThisMonth = endOfMonth(now);
|
|
||||||
const totalMinutesInMonth = 60 * 70 * 4; // 60min * 70h (35*2) * 4 Weeks
|
|
||||||
|
|
||||||
const usedTimePerPrinter: Record<string, number> = pJobs.reduce(
|
|
||||||
(acc, job) => {
|
|
||||||
const jobStart = new Date(job.startAt);
|
|
||||||
if (jobStart >= startOfThisMonth && jobStart <= endOfThisMonth) {
|
|
||||||
acc[job.printerId] = (acc[job.printerId] || 0) + job.durationInMinutes;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as Record<string, number>,
|
|
||||||
);
|
|
||||||
|
|
||||||
return p.map((printer) => {
|
|
||||||
const usedTime = usedTimePerPrinter[printer.id] || 0;
|
|
||||||
const idleTime = totalMinutesInMonth - usedTime;
|
|
||||||
const averageIdleTime = idleTime < 0 ? 0 : idleTime; // Ensure no negative idle time
|
|
||||||
|
|
||||||
return {
|
|
||||||
printerId: printer.id,
|
|
||||||
printerName: printer.name,
|
|
||||||
averageIdleTime,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
@ -15,10 +15,12 @@ export function calculatePrinterUtilization(jobs: InferResultType<"printJobs", {
|
|||||||
{} as Record<string, number>,
|
{} as Record<string, number>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const totalTimeInMinutes = 60 * 70 * 4; // 60 Minutes * 70h * 4 Weeks
|
const totalTimeInMinutes = 60 * 35 * 3; // 60 Minutes * 35h * 3 Weeks
|
||||||
|
// 35h Woche, 3 mal in der Woche in TBA
|
||||||
|
|
||||||
const printerUtilizationPercentage = Object.keys(usedTimePerPrinter).map((printerId) => {
|
const printerUtilizationPercentage = Object.keys(usedTimePerPrinter).map((printerId) => {
|
||||||
const usedTime = usedTimePerPrinter[printerId];
|
const usedTime = usedTimePerPrinter[printerId];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
printerId,
|
printerId,
|
||||||
name: printers[printerId],
|
name: printers[printerId],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user