"feat: Added debug server and related components for improved development experience"
This commit is contained in:
54
frontend/src/utils/analytics/error-rate.ts
Normal file
54
frontend/src/utils/analytics/error-rate.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { printJobs } from "@/server/db/schema";
|
||||
import type { InferResultType } from "@/utils/drizzle";
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
|
||||
export interface PrinterErrorRate {
|
||||
printerId: string;
|
||||
name: string;
|
||||
errorRate: number; // Error rate as a percentage (0-100)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the error rate for print jobs aggregated by printer as a percentage.
|
||||
*
|
||||
* @param pJobs - Array of print job objects.
|
||||
* @returns An array of PrinterErrorRate objects, each containing the printer ID and its error rate.
|
||||
*/
|
||||
export function calculatePrinterErrorRate(
|
||||
pJobs: InferResultType<"printJobs", { printer: true }>[],
|
||||
): PrinterErrorRate[] {
|
||||
if (pJobs.length === 0) {
|
||||
return []; // No jobs, no data.
|
||||
}
|
||||
const printers = pJobs.map((job) => job.printer);
|
||||
|
||||
// Group jobs by printer ID
|
||||
const jobsByPrinter: Record<string, InferSelectModel<typeof printJobs>[]> = pJobs.reduce(
|
||||
(acc, job) => {
|
||||
if (!acc[job.printerId]) {
|
||||
acc[job.printerId] = [];
|
||||
}
|
||||
acc[job.printerId].push(job);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, InferSelectModel<typeof printJobs>[]>,
|
||||
);
|
||||
|
||||
// Calculate the error rate for each printer
|
||||
const printerErrorRates: PrinterErrorRate[] = Object.entries(jobsByPrinter).map(([printerId, jobs]) => {
|
||||
const totalJobs = jobs.length;
|
||||
const abortedJobsCount = jobs.filter((job) => job.aborted).length;
|
||||
const errorRate = (abortedJobsCount / totalJobs) * 100;
|
||||
|
||||
const printer = printers.find((printer) => printer.id === printerId);
|
||||
const printerName = printer ? printer.name : "Unbekannter Drucker";
|
||||
|
||||
return {
|
||||
printerId,
|
||||
name: printerName,
|
||||
errorRate: Number.parseFloat(errorRate.toFixed(2)), // Rounded to two decimal places
|
||||
};
|
||||
});
|
||||
|
||||
return printerErrorRates;
|
||||
}
|
39
frontend/src/utils/analytics/errors.ts
Normal file
39
frontend/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,
|
||||
}));
|
||||
}
|
97
frontend/src/utils/analytics/forecast.ts
Normal file
97
frontend/src/utils/analytics/forecast.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import type { InferResultType } from "@/utils/drizzle";
|
||||
|
||||
type UsagePerDay = {
|
||||
day: number; // 0 (Sunday) to 6 (Saturday)
|
||||
usageMinutes: number;
|
||||
};
|
||||
|
||||
function aggregateUsageByDay(jobs: InferResultType<"printJobs">[]): {
|
||||
usageData: UsagePerDay[];
|
||||
earliestDate: Date;
|
||||
latestDate: Date;
|
||||
} {
|
||||
const usagePerDayMap = new Map<number, number>();
|
||||
|
||||
let earliestDate: Date | null = null;
|
||||
let latestDate: Date | null = null;
|
||||
|
||||
for (const job of jobs) {
|
||||
let remainingDuration = job.durationInMinutes;
|
||||
const currentStart = new Date(job.startAt);
|
||||
|
||||
// Update earliest and latest dates
|
||||
if (!earliestDate || currentStart < earliestDate) {
|
||||
earliestDate = new Date(currentStart);
|
||||
}
|
||||
const jobEnd = new Date(currentStart);
|
||||
jobEnd.setMinutes(jobEnd.getMinutes() + job.durationInMinutes);
|
||||
if (!latestDate || jobEnd > latestDate) {
|
||||
latestDate = new Date(jobEnd);
|
||||
}
|
||||
|
||||
while (remainingDuration > 0) {
|
||||
const day = currentStart.getDay();
|
||||
|
||||
// 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,
|
||||
}));
|
||||
|
||||
if (earliestDate === null) {
|
||||
earliestDate = new Date();
|
||||
}
|
||||
|
||||
if (latestDate === null) {
|
||||
latestDate = new Date();
|
||||
}
|
||||
|
||||
return { usageData, earliestDate: earliestDate, latestDate: latestDate };
|
||||
}
|
||||
|
||||
function countWeekdays(startDate: Date, endDate: Date): number[] {
|
||||
const countPerDay = Array(7).fill(0);
|
||||
const currentDate = new Date(startDate);
|
||||
currentDate.setHours(0, 0, 0, 0); // Ensure starting at midnight
|
||||
endDate.setHours(0, 0, 0, 0); // Ensure ending at midnight
|
||||
|
||||
while (currentDate <= endDate) {
|
||||
const day = currentDate.getDay();
|
||||
countPerDay[day]++;
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
}
|
||||
return countPerDay;
|
||||
}
|
||||
|
||||
export function forecastPrinterUsage(jobs: InferResultType<"printJobs">[]): number[] {
|
||||
const { usageData, earliestDate, latestDate } = aggregateUsageByDay(jobs);
|
||||
|
||||
// Count the number of times each weekday occurs in the data period
|
||||
const weekdaysCount = countWeekdays(earliestDate, latestDate);
|
||||
|
||||
const forecasts: number[] = [];
|
||||
for (const data of usageData) {
|
||||
const dayCount = weekdaysCount[data.day];
|
||||
let usagePrediction = data.usageMinutes / dayCount;
|
||||
if (Number.isNaN(usagePrediction)) {
|
||||
usagePrediction = 0;
|
||||
}
|
||||
forecasts.push(Math.round(usagePrediction));
|
||||
}
|
||||
|
||||
return forecasts;
|
||||
}
|
32
frontend/src/utils/analytics/utilization.ts
Normal file
32
frontend/src/utils/analytics/utilization.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { InferResultType } from "@/utils/drizzle";
|
||||
|
||||
export function calculatePrinterUtilization(jobs: InferResultType<"printJobs", { printer: true }>[]) {
|
||||
const printers = jobs.reduce<Record<string, string>>((acc, job) => {
|
||||
acc[job.printerId] = job.printer.name;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const usedTimePerPrinter: Record<string, number> = jobs.reduce(
|
||||
(acc, job) => {
|
||||
acc[job.printer.id] = (acc[job.printer.id] || 0) + job.durationInMinutes;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>,
|
||||
);
|
||||
|
||||
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 usedTime = usedTimePerPrinter[printerId];
|
||||
|
||||
return {
|
||||
printerId,
|
||||
name: printers[printerId],
|
||||
utilizationPercentage: usedTime / totalTimeInMinutes,
|
||||
};
|
||||
});
|
||||
|
||||
return printerUtilizationPercentage;
|
||||
}
|
52
frontend/src/utils/analytics/volume.ts
Normal file
52
frontend/src/utils/analytics/volume.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { printJobs } from "@/server/db/schema";
|
||||
import { endOfDay, endOfMonth, endOfWeek, startOfDay, startOfMonth, startOfWeek } from "date-fns";
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
|
||||
interface PrintVolumes {
|
||||
today: number;
|
||||
thisWeek: number;
|
||||
thisMonth: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of print jobs for today, this week, and this month.
|
||||
*
|
||||
* @param printJobs - Array of print job objects.
|
||||
* @returns An object with counts of print jobs for today, this week, and this month.
|
||||
*/
|
||||
export function calculatePrintVolumes(pJobs: InferSelectModel<typeof printJobs>[]): PrintVolumes {
|
||||
const now = new Date();
|
||||
|
||||
// Define time ranges with week starting on Monday
|
||||
const timeRanges = {
|
||||
today: { start: startOfDay(now), end: endOfDay(now) },
|
||||
thisWeek: {
|
||||
start: startOfWeek(now, { weekStartsOn: 1 }),
|
||||
end: endOfWeek(now, { weekStartsOn: 1 }),
|
||||
},
|
||||
thisMonth: { start: startOfMonth(now), end: endOfMonth(now) },
|
||||
};
|
||||
|
||||
// Initialize counts
|
||||
const volumes: PrintVolumes = {
|
||||
today: 0,
|
||||
thisWeek: 0,
|
||||
thisMonth: 0,
|
||||
};
|
||||
|
||||
// Iterate over print jobs and count based on time ranges
|
||||
for (const job of pJobs) {
|
||||
const jobStart = new Date(job.startAt);
|
||||
if (jobStart >= timeRanges.today.start && jobStart <= timeRanges.today.end) {
|
||||
volumes.today += 1;
|
||||
}
|
||||
if (jobStart >= timeRanges.thisWeek.start && jobStart <= timeRanges.thisWeek.end) {
|
||||
volumes.thisWeek += 1;
|
||||
}
|
||||
if (jobStart >= timeRanges.thisMonth.start && jobStart <= timeRanges.thisMonth.end) {
|
||||
volumes.thisMonth += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return volumes;
|
||||
}
|
51
frontend/src/utils/api-config.ts
Normal file
51
frontend/src/utils/api-config.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// Basis-URL für Backend-API
|
||||
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://192.168.0.105:5000";
|
||||
|
||||
// Frontend-URL für Callbacks - unterstützt mehrere Domains
|
||||
const getFrontendUrl = () => {
|
||||
// Priorität 1: Explizit gesetzte Umgebungsvariable
|
||||
if (process.env.NEXT_PUBLIC_FRONTEND_URL) {
|
||||
return process.env.NEXT_PUBLIC_FRONTEND_URL;
|
||||
}
|
||||
|
||||
// Priorität 2: Spezifischer Hostname für das Netzwerk
|
||||
if (typeof window !== 'undefined') {
|
||||
// Im Browser: Prüfen auf m040tbaraspi001.de040.corpintra.net
|
||||
const hostname = window.location.hostname;
|
||||
if (hostname === 'm040tbaraspi001' ||
|
||||
hostname === 'm040tbaraspi001.de040.corpintra.net' ||
|
||||
hostname.includes('corpintra.net')) {
|
||||
return `http://${hostname}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Priorität 3: Default für Localhost
|
||||
return "http://localhost:3000";
|
||||
};
|
||||
|
||||
export const FRONTEND_URL = getFrontendUrl();
|
||||
|
||||
// OAuth Callback URL - muss exakt mit der registrierten URL in GitHub übereinstimmen
|
||||
export const OAUTH_CALLBACK_URL = process.env.NEXT_PUBLIC_OAUTH_CALLBACK_URL ||
|
||||
`${FRONTEND_URL}/auth/login/callback`;
|
||||
|
||||
// Liste der erlaubten Hostnamen für OAuth-Callbacks
|
||||
export const ALLOWED_CALLBACK_HOSTS = [
|
||||
'localhost',
|
||||
'm040tbaraspi001',
|
||||
'm040tbaraspi001.de040.corpintra.net',
|
||||
'192.168.0.105'
|
||||
];
|
||||
|
||||
// Endpunkte für die verschiedenen Ressourcen
|
||||
export const API_ENDPOINTS = {
|
||||
PRINTERS: `${API_BASE_URL}/api/printers`,
|
||||
JOBS: `${API_BASE_URL}/api/jobs`,
|
||||
USERS: `${API_BASE_URL}/api/users`,
|
||||
|
||||
// OAuth-spezifische Endpunkte
|
||||
AUTH: {
|
||||
LOGIN: `${API_BASE_URL}/api/auth/login`,
|
||||
CALLBACK: `${API_BASE_URL}/api/auth/callback`,
|
||||
}
|
||||
};
|
29
frontend/src/utils/drizzle.ts
Normal file
29
frontend/src/utils/drizzle.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type * as schema from "@/server/db/schema";
|
||||
import type { BuildQueryResult, DBQueryConfig, ExtractTablesWithRelations } from "drizzle-orm";
|
||||
|
||||
type Schema = typeof schema;
|
||||
type TSchema = ExtractTablesWithRelations<Schema>;
|
||||
|
||||
/**
|
||||
* Infer the relation type of a table.
|
||||
*/
|
||||
export type IncludeRelation<TableName extends keyof TSchema> = DBQueryConfig<
|
||||
"one" | "many",
|
||||
boolean,
|
||||
TSchema,
|
||||
TSchema[TableName]
|
||||
>["with"];
|
||||
|
||||
/**
|
||||
* Infer the result type of a query with optional relations.
|
||||
*/
|
||||
export type InferResultType<
|
||||
TableName extends keyof TSchema,
|
||||
With extends IncludeRelation<TableName> | undefined = undefined,
|
||||
> = BuildQueryResult<
|
||||
TSchema,
|
||||
TSchema[TableName],
|
||||
{
|
||||
with: With;
|
||||
}
|
||||
>;
|
59
frontend/src/utils/errors.ts
Normal file
59
frontend/src/utils/errors.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import strings from "@/utils/strings";
|
||||
|
||||
/**
|
||||
* Base error class.
|
||||
*/
|
||||
class BaseError extends Error {
|
||||
constructor(message?: string) {
|
||||
// Pass the message to the Error constructor
|
||||
super(message);
|
||||
|
||||
// Set the name of the error
|
||||
this.name = this.constructor.name;
|
||||
|
||||
// Capture the stack trace
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
} else {
|
||||
this.stack = new Error(message).stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission error class.
|
||||
*/
|
||||
export class PermissionError extends BaseError {
|
||||
constructor() {
|
||||
super(strings.ERROR.PERMISSION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication error class.
|
||||
*/
|
||||
export class AuthenticationError extends BaseError {
|
||||
constructor() {
|
||||
super(strings.ERROR.NO_SESSION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation error class.
|
||||
*/
|
||||
export class ValidationError extends BaseError {
|
||||
constructor() {
|
||||
super(strings.ERROR.VALIDATION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not found error class.
|
||||
*/
|
||||
export class NotFoundError extends BaseError {
|
||||
constructor() {
|
||||
super(strings.ERROR.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseError;
|
78
frontend/src/utils/external-api.ts
Normal file
78
frontend/src/utils/external-api.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { API_ENDPOINTS } from './api-config';
|
||||
|
||||
// Typdefinitionen für API-Responses
|
||||
export interface Printer {
|
||||
id: string;
|
||||
name: string;
|
||||
ip: string;
|
||||
status: string;
|
||||
is_enabled: boolean;
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
id: string;
|
||||
printer_id: string;
|
||||
user_id: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
// Fetcher für SWR mit Fehlerbehandlung
|
||||
const fetchWithErrorHandling = async (url: string) => {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = new Error('Ein Fehler ist bei der API-Anfrage aufgetreten');
|
||||
throw error;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
// API-Funktionen
|
||||
export const api = {
|
||||
// Drucker-Endpunkte
|
||||
printers: {
|
||||
getAll: () => fetchWithErrorHandling(API_ENDPOINTS.PRINTERS),
|
||||
getById: (id: string) => fetchWithErrorHandling(`${API_ENDPOINTS.PRINTERS}/${id}`),
|
||||
create: (data: Partial<Printer>) =>
|
||||
fetch(API_ENDPOINTS.PRINTERS, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}).then(res => res.json()),
|
||||
update: (id: string, data: Partial<Printer>) =>
|
||||
fetch(`${API_ENDPOINTS.PRINTERS}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}).then(res => res.json()),
|
||||
delete: (id: string) =>
|
||||
fetch(`${API_ENDPOINTS.PRINTERS}/${id}`, {
|
||||
method: 'DELETE',
|
||||
}).then(res => res.json()),
|
||||
},
|
||||
|
||||
// Jobs-Endpunkte
|
||||
jobs: {
|
||||
getAll: () => fetchWithErrorHandling(API_ENDPOINTS.JOBS),
|
||||
getById: (id: string) => fetchWithErrorHandling(`${API_ENDPOINTS.JOBS}/${id}`),
|
||||
create: (data: Partial<Job>) =>
|
||||
fetch(API_ENDPOINTS.JOBS, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}).then(res => res.json()),
|
||||
update: (id: string, data: Partial<Job>) =>
|
||||
fetch(`${API_ENDPOINTS.JOBS}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}).then(res => res.json()),
|
||||
delete: (id: string) =>
|
||||
fetch(`${API_ENDPOINTS.JOBS}/${id}`, {
|
||||
method: 'DELETE',
|
||||
}).then(res => res.json()),
|
||||
},
|
||||
};
|
1
frontend/src/utils/fetch.ts
Normal file
1
frontend/src/utils/fetch.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const fetcher = (url: string) => fetch(url).then((response) => response.json());
|
35
frontend/src/utils/guard.ts
Normal file
35
frontend/src/utils/guard.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { UserRole } from "@/server/auth/permissions";
|
||||
import type { users } from "@/server/db/schema";
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
import type { RegisteredDatabaseUserAttributes } from "lucia";
|
||||
|
||||
// Constants for better readability
|
||||
export const IS = false;
|
||||
export const IS_NOT = true;
|
||||
|
||||
/**
|
||||
* Checks if a user has the required role(s).
|
||||
* @param user - The user to check.
|
||||
* @param negate - Whether to negate the result.
|
||||
* @param roleRequirements - The required role(s).
|
||||
* @returns Whether the user has the required role(s).
|
||||
*/
|
||||
export function guard(
|
||||
user: RegisteredDatabaseUserAttributes | InferSelectModel<typeof users> | undefined | null,
|
||||
negate: boolean,
|
||||
roleRequirements: UserRole | UserRole[],
|
||||
) {
|
||||
// Early return for unauthenticated users
|
||||
if (!user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normalize roleRequirements to an array
|
||||
const requiredRoles = Array.isArray(roleRequirements) ? roleRequirements : [roleRequirements];
|
||||
|
||||
// Check if the user's role is in the required roles
|
||||
const userHasRequiredRole = requiredRoles.includes(user.role as UserRole);
|
||||
|
||||
// Return the result, negated if necessary
|
||||
return negate ? !userHasRequiredRole : userHasRequiredRole;
|
||||
}
|
41
frontend/src/utils/printers.ts
Normal file
41
frontend/src/utils/printers.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { InferResultType } from "@/utils/drizzle";
|
||||
|
||||
export enum PrinterStatus {
|
||||
IDLE = 0,
|
||||
OUT_OF_ORDER = 1,
|
||||
RESERVED = 2,
|
||||
}
|
||||
|
||||
export function derivePrinterStatus(
|
||||
printer: InferResultType<"printers", { printJobs: true }>,
|
||||
) {
|
||||
if (printer.status === PrinterStatus.OUT_OF_ORDER) {
|
||||
return PrinterStatus.OUT_OF_ORDER;
|
||||
}
|
||||
|
||||
const activePrintJob = printer.printJobs[0];
|
||||
|
||||
if (!activePrintJob || activePrintJob.aborted) {
|
||||
return PrinterStatus.IDLE;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const startAt = new Date(activePrintJob.startAt).getTime();
|
||||
const endAt = startAt + activePrintJob.durationInMinutes * 60 * 1000;
|
||||
if (now < endAt) {
|
||||
return PrinterStatus.RESERVED;
|
||||
}
|
||||
|
||||
return PrinterStatus.IDLE;
|
||||
}
|
||||
|
||||
export function translatePrinterStatus(status: PrinterStatus) {
|
||||
switch (status) {
|
||||
case PrinterStatus.IDLE:
|
||||
return "Verfügbar";
|
||||
case PrinterStatus.OUT_OF_ORDER:
|
||||
return "Außer Betrieb";
|
||||
case PrinterStatus.RESERVED:
|
||||
return "Reserviert";
|
||||
}
|
||||
}
|
11
frontend/src/utils/strings.ts
Normal file
11
frontend/src/utils/strings.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Contains all strings used in the application.
|
||||
*/
|
||||
export default {
|
||||
ERROR: {
|
||||
PERMISSION: "Du besitzt nicht die erforderlichen Berechtigungen um diese Aktion auszuführen.",
|
||||
VALIDATION: "Die Eingabe ist ungültig.",
|
||||
NOT_FOUND: "Die angeforderten Daten konnten nicht gefunden werden.",
|
||||
NO_SESSION: "Du bist nicht angemeldet.",
|
||||
},
|
||||
};
|
11
frontend/src/utils/styles.ts
Normal file
11
frontend/src/utils/styles.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
/**
|
||||
* Utility function to merge classes with tailwindcss.
|
||||
* @param inputs Classes to merge
|
||||
* @returns classes
|
||||
*/
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
Reference in New Issue
Block a user