This commit is contained in:
xCyanGrizzly
2026-02-18 14:26:36 +01:00
commit 3a5726e82b
167 changed files with 104081 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
import { prisma } from "@/lib/prisma";
interface LowStockItem {
id: string;
name: string;
type: "filament" | "resin" | "paint";
colorHex: string;
remaining: number;
total: number;
percent: number;
}
interface RecentUsage {
id: string;
itemType: string;
amount: number;
unit: string;
notes: string | null;
createdAt: Date;
itemName: string;
}
export interface DashboardStats {
totalItems: number;
inventoryValue: number;
lowStockCount: number;
recentActivityCount: number;
lowStockItems: LowStockItem[];
recentUsage: RecentUsage[];
}
export async function getDashboardStats(
userId: string,
lowStockThreshold: number
): Promise<DashboardStats> {
const [filaments, resins, paints, usageLogs24h, recentLogs] = await Promise.all([
prisma.filament.findMany({
where: { userId, archived: false },
select: {
id: true,
name: true,
colorHex: true,
spoolWeight: true,
usedWeight: true,
cost: true,
},
}),
prisma.resin.findMany({
where: { userId, archived: false },
select: {
id: true,
name: true,
colorHex: true,
bottleSize: true,
usedML: true,
cost: true,
},
}),
prisma.paint.findMany({
where: { userId, archived: false },
select: {
id: true,
name: true,
colorHex: true,
volumeML: true,
usedML: true,
cost: true,
},
}),
prisma.usageLog.count({
where: {
userId,
createdAt: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) },
},
}),
prisma.usageLog.findMany({
where: { userId },
orderBy: { createdAt: "desc" },
take: 10,
include: {
filament: { select: { name: true } },
resin: { select: { name: true } },
paint: { select: { name: true } },
},
}),
]);
const totalItems = filaments.length + resins.length + paints.length;
const inventoryValue =
filaments.reduce((sum: number, f: { cost: number | null }) => sum + (f.cost ?? 0), 0) +
resins.reduce((sum: number, r: { cost: number | null }) => sum + (r.cost ?? 0), 0) +
paints.reduce((sum: number, p: { cost: number | null }) => sum + (p.cost ?? 0), 0);
const lowStockItems: LowStockItem[] = [];
for (const f of filaments) {
const remaining = f.spoolWeight - f.usedWeight;
const percent = f.spoolWeight > 0 ? (remaining / f.spoolWeight) * 100 : 0;
if (percent <= lowStockThreshold && percent > 0) {
lowStockItems.push({
id: f.id,
name: f.name,
type: "filament",
colorHex: f.colorHex,
remaining,
total: f.spoolWeight,
percent,
});
}
}
for (const r of resins) {
const remaining = r.bottleSize - r.usedML;
const percent = r.bottleSize > 0 ? (remaining / r.bottleSize) * 100 : 0;
if (percent <= lowStockThreshold && percent > 0) {
lowStockItems.push({
id: r.id,
name: r.name,
type: "resin",
colorHex: r.colorHex,
remaining,
total: r.bottleSize,
percent,
});
}
}
for (const p of paints) {
const remaining = p.volumeML - p.usedML;
const percent = p.volumeML > 0 ? (remaining / p.volumeML) * 100 : 0;
if (percent <= lowStockThreshold && percent > 0) {
lowStockItems.push({
id: p.id,
name: p.name,
type: "paint",
colorHex: p.colorHex,
remaining,
total: p.volumeML,
percent,
});
}
}
lowStockItems.sort((a, b) => a.percent - b.percent);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const recentUsage: RecentUsage[] = recentLogs.map((log: any) => ({
id: log.id,
itemType: log.itemType,
amount: log.amount,
unit: log.unit,
notes: log.notes,
createdAt: log.createdAt,
itemName:
log.filament?.name ?? log.resin?.name ?? log.paint?.name ?? "Unknown",
}));
return {
totalItems,
inventoryValue,
lowStockCount: lowStockItems.length,
recentActivityCount: usageLogs24h,
lowStockItems,
recentUsage,
};
}