mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 06:11:15 +00:00
197 lines
4.9 KiB
TypeScript
197 lines
4.9 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
|
|
|
interface LowStockItem {
|
|
id: string;
|
|
name: string;
|
|
type: "filament" | "resin" | "paint" | "supply";
|
|
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, supplies, 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.supply.findMany({
|
|
where: { userId, archived: false },
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
colorHex: true,
|
|
totalAmount: true,
|
|
usedAmount: 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 } },
|
|
supply: { select: { name: true } },
|
|
},
|
|
}),
|
|
]);
|
|
|
|
const totalItems = filaments.length + resins.length + paints.length + supplies.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) +
|
|
supplies.reduce((sum: number, s: { cost: number | null }) => sum + (s.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,
|
|
});
|
|
}
|
|
}
|
|
|
|
for (const s of supplies) {
|
|
const remaining = s.totalAmount - s.usedAmount;
|
|
const percent = s.totalAmount > 0 ? (remaining / s.totalAmount) * 100 : 0;
|
|
if (percent <= lowStockThreshold && percent > 0) {
|
|
lowStockItems.push({
|
|
id: s.id,
|
|
name: s.name,
|
|
type: "supply",
|
|
colorHex: s.colorHex ?? "#6b7280",
|
|
remaining,
|
|
total: s.totalAmount,
|
|
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 ?? log.supply?.name ?? "Unknown",
|
|
}));
|
|
|
|
return {
|
|
totalItems,
|
|
inventoryValue,
|
|
lowStockCount: lowStockItems.length,
|
|
recentActivityCount: usageLogs24h,
|
|
lowStockItems,
|
|
recentUsage,
|
|
};
|
|
}
|