mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 06:11:15 +00:00
Init
This commit is contained in:
77145
src/data/catalog/paints.json
Normal file
77145
src/data/catalog/paints.json
Normal file
File diff suppressed because it is too large
Load Diff
167
src/data/dashboard.queries.ts
Normal file
167
src/data/dashboard.queries.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
81
src/data/filament.queries.ts
Normal file
81
src/data/filament.queries.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { Prisma } from "@/generated/prisma";
|
||||
import type { DataTableSearchParams } from "@/types/table.types";
|
||||
|
||||
interface FilamentSearchParams extends DataTableSearchParams {
|
||||
material?: string | string[];
|
||||
vendor?: string | string[];
|
||||
location?: string | string[];
|
||||
}
|
||||
|
||||
export async function getFilaments(userId: string, params: FilamentSearchParams) {
|
||||
const page = Number(params.page) || 1;
|
||||
const perPage = Number(params.perPage) || 20;
|
||||
const skip = (page - 1) * perPage;
|
||||
|
||||
const materials = Array.isArray(params.material)
|
||||
? params.material
|
||||
: params.material
|
||||
? [params.material]
|
||||
: [];
|
||||
const vendorIds = Array.isArray(params.vendor)
|
||||
? params.vendor
|
||||
: params.vendor
|
||||
? [params.vendor]
|
||||
: [];
|
||||
const locationIds = Array.isArray(params.location)
|
||||
? params.location
|
||||
: params.location
|
||||
? [params.location]
|
||||
: [];
|
||||
|
||||
const where: Prisma.FilamentWhereInput = {
|
||||
userId,
|
||||
archived: params.archived === "true" ? undefined : false,
|
||||
...(params.search && {
|
||||
OR: [
|
||||
{ name: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{ brand: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{ color: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
],
|
||||
}),
|
||||
...(materials.length > 0 && { material: { in: materials } }),
|
||||
...(vendorIds.length > 0 && { vendorId: { in: vendorIds } }),
|
||||
...(locationIds.length > 0 && { locationId: { in: locationIds } }),
|
||||
};
|
||||
|
||||
const sortField = params.sort || "createdAt";
|
||||
const sortOrder = params.order || "desc";
|
||||
|
||||
const [data, totalCount] = await Promise.all([
|
||||
prisma.filament.findMany({
|
||||
where,
|
||||
orderBy: { [sortField]: sortOrder },
|
||||
skip,
|
||||
take: perPage,
|
||||
include: {
|
||||
vendor: { select: { id: true, name: true } },
|
||||
location: { select: { id: true, name: true } },
|
||||
tags: { include: { tag: { select: { id: true, name: true } } } },
|
||||
},
|
||||
}),
|
||||
prisma.filament.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
pageCount: Math.ceil(totalCount / perPage),
|
||||
totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getFilamentById(id: string, userId: string) {
|
||||
return prisma.filament.findFirst({
|
||||
where: { id, userId },
|
||||
include: {
|
||||
vendor: { select: { id: true, name: true } },
|
||||
location: { select: { id: true, name: true } },
|
||||
tags: { include: { tag: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
59
src/data/location.queries.ts
Normal file
59
src/data/location.queries.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { Prisma } from "@/generated/prisma";
|
||||
import type { DataTableSearchParams } from "@/types/table.types";
|
||||
|
||||
export async function getLocations(userId: string, params: DataTableSearchParams) {
|
||||
const page = Number(params.page) || 1;
|
||||
const perPage = Number(params.perPage) || 20;
|
||||
const skip = (page - 1) * perPage;
|
||||
|
||||
const where: Prisma.LocationWhereInput = {
|
||||
userId,
|
||||
archived: params.archived === "true" ? undefined : false,
|
||||
...(params.search && {
|
||||
OR: [
|
||||
{ name: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{ description: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
const sortField = params.sort || "createdAt";
|
||||
const sortOrder = params.order || "desc";
|
||||
|
||||
const [data, totalCount] = await Promise.all([
|
||||
prisma.location.findMany({
|
||||
where,
|
||||
orderBy: { [sortField]: sortOrder },
|
||||
skip,
|
||||
take: perPage,
|
||||
include: {
|
||||
_count: { select: { filaments: true, resins: true, paints: true } },
|
||||
},
|
||||
}),
|
||||
prisma.location.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
pageCount: Math.ceil(totalCount / perPage),
|
||||
totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getLocationById(id: string, userId: string) {
|
||||
return prisma.location.findFirst({
|
||||
where: { id, userId },
|
||||
include: {
|
||||
_count: { select: { filaments: true, resins: true, paints: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getLocationOptions(userId: string) {
|
||||
return prisma.location.findMany({
|
||||
where: { userId, archived: false },
|
||||
select: { id: true, name: true },
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
}
|
||||
81
src/data/paint.queries.ts
Normal file
81
src/data/paint.queries.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { Prisma } from "@/generated/prisma";
|
||||
import type { DataTableSearchParams } from "@/types/table.types";
|
||||
|
||||
interface PaintSearchParams extends DataTableSearchParams {
|
||||
finish?: string | string[];
|
||||
vendor?: string | string[];
|
||||
location?: string | string[];
|
||||
}
|
||||
|
||||
export async function getPaints(userId: string, params: PaintSearchParams) {
|
||||
const page = Number(params.page) || 1;
|
||||
const perPage = Number(params.perPage) || 20;
|
||||
const skip = (page - 1) * perPage;
|
||||
|
||||
const finishes = Array.isArray(params.finish)
|
||||
? params.finish
|
||||
: params.finish
|
||||
? [params.finish]
|
||||
: [];
|
||||
const vendorIds = Array.isArray(params.vendor)
|
||||
? params.vendor
|
||||
: params.vendor
|
||||
? [params.vendor]
|
||||
: [];
|
||||
const locationIds = Array.isArray(params.location)
|
||||
? params.location
|
||||
: params.location
|
||||
? [params.location]
|
||||
: [];
|
||||
|
||||
const where: Prisma.PaintWhereInput = {
|
||||
userId,
|
||||
archived: params.archived === "true" ? undefined : false,
|
||||
...(params.search && {
|
||||
OR: [
|
||||
{ name: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{ brand: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{ color: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{ line: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
],
|
||||
}),
|
||||
...(finishes.length > 0 && { finish: { in: finishes } }),
|
||||
...(vendorIds.length > 0 && { vendorId: { in: vendorIds } }),
|
||||
...(locationIds.length > 0 && { locationId: { in: locationIds } }),
|
||||
};
|
||||
|
||||
const sortField = params.sort || "createdAt";
|
||||
const sortOrder = params.order || "desc";
|
||||
|
||||
const [data, totalCount] = await Promise.all([
|
||||
prisma.paint.findMany({
|
||||
where,
|
||||
orderBy: { [sortField]: sortOrder },
|
||||
skip,
|
||||
take: perPage,
|
||||
include: {
|
||||
vendor: { select: { id: true, name: true } },
|
||||
location: { select: { id: true, name: true } },
|
||||
},
|
||||
}),
|
||||
prisma.paint.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
pageCount: Math.ceil(totalCount / perPage),
|
||||
totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getPaintById(id: string, userId: string) {
|
||||
return prisma.paint.findFirst({
|
||||
where: { id, userId },
|
||||
include: {
|
||||
vendor: { select: { id: true, name: true } },
|
||||
location: { select: { id: true, name: true } },
|
||||
tags: { include: { tag: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
80
src/data/resin.queries.ts
Normal file
80
src/data/resin.queries.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { Prisma } from "@/generated/prisma";
|
||||
import type { DataTableSearchParams } from "@/types/table.types";
|
||||
|
||||
interface ResinSearchParams extends DataTableSearchParams {
|
||||
resinType?: string | string[];
|
||||
vendor?: string | string[];
|
||||
location?: string | string[];
|
||||
}
|
||||
|
||||
export async function getResins(userId: string, params: ResinSearchParams) {
|
||||
const page = Number(params.page) || 1;
|
||||
const perPage = Number(params.perPage) || 20;
|
||||
const skip = (page - 1) * perPage;
|
||||
|
||||
const resinTypes = Array.isArray(params.resinType)
|
||||
? params.resinType
|
||||
: params.resinType
|
||||
? [params.resinType]
|
||||
: [];
|
||||
const vendorIds = Array.isArray(params.vendor)
|
||||
? params.vendor
|
||||
: params.vendor
|
||||
? [params.vendor]
|
||||
: [];
|
||||
const locationIds = Array.isArray(params.location)
|
||||
? params.location
|
||||
: params.location
|
||||
? [params.location]
|
||||
: [];
|
||||
|
||||
const where: Prisma.ResinWhereInput = {
|
||||
userId,
|
||||
archived: params.archived === "true" ? undefined : false,
|
||||
...(params.search && {
|
||||
OR: [
|
||||
{ name: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{ brand: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{ color: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
],
|
||||
}),
|
||||
...(resinTypes.length > 0 && { resinType: { in: resinTypes } }),
|
||||
...(vendorIds.length > 0 && { vendorId: { in: vendorIds } }),
|
||||
...(locationIds.length > 0 && { locationId: { in: locationIds } }),
|
||||
};
|
||||
|
||||
const sortField = params.sort || "createdAt";
|
||||
const sortOrder = params.order || "desc";
|
||||
|
||||
const [data, totalCount] = await Promise.all([
|
||||
prisma.resin.findMany({
|
||||
where,
|
||||
orderBy: { [sortField]: sortOrder },
|
||||
skip,
|
||||
take: perPage,
|
||||
include: {
|
||||
vendor: { select: { id: true, name: true } },
|
||||
location: { select: { id: true, name: true } },
|
||||
},
|
||||
}),
|
||||
prisma.resin.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
pageCount: Math.ceil(totalCount / perPage),
|
||||
totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getResinById(id: string, userId: string) {
|
||||
return prisma.resin.findFirst({
|
||||
where: { id, userId },
|
||||
include: {
|
||||
vendor: { select: { id: true, name: true } },
|
||||
location: { select: { id: true, name: true } },
|
||||
tags: { include: { tag: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
43
src/data/settings.queries.ts
Normal file
43
src/data/settings.queries.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function getUserSettings(userId: string) {
|
||||
let settings = await prisma.userSettings.findUnique({
|
||||
where: { userId },
|
||||
});
|
||||
|
||||
if (!settings) {
|
||||
settings = await prisma.userSettings.create({
|
||||
data: {
|
||||
userId,
|
||||
lowStockThreshold: 20,
|
||||
currency: "EUR",
|
||||
theme: "dark",
|
||||
units: "metric",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
export async function updateUserSettings(
|
||||
userId: string,
|
||||
data: {
|
||||
lowStockThreshold?: number;
|
||||
currency?: string;
|
||||
theme?: string;
|
||||
units?: string;
|
||||
}
|
||||
) {
|
||||
return prisma.userSettings.upsert({
|
||||
where: { userId },
|
||||
update: data,
|
||||
create: {
|
||||
userId,
|
||||
lowStockThreshold: data.lowStockThreshold ?? 20,
|
||||
currency: data.currency ?? "EUR",
|
||||
theme: data.theme ?? "dark",
|
||||
units: data.units ?? "metric",
|
||||
},
|
||||
});
|
||||
}
|
||||
59
src/data/vendor.queries.ts
Normal file
59
src/data/vendor.queries.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { Prisma } from "@/generated/prisma";
|
||||
import type { DataTableSearchParams } from "@/types/table.types";
|
||||
|
||||
export async function getVendors(userId: string, params: DataTableSearchParams) {
|
||||
const page = Number(params.page) || 1;
|
||||
const perPage = Number(params.perPage) || 20;
|
||||
const skip = (page - 1) * perPage;
|
||||
|
||||
const where: Prisma.VendorWhereInput = {
|
||||
userId,
|
||||
archived: params.archived === "true" ? undefined : false,
|
||||
...(params.search && {
|
||||
OR: [
|
||||
{ name: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
{ notes: { contains: params.search, mode: "insensitive" as Prisma.QueryMode } },
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
const sortField = params.sort || "createdAt";
|
||||
const sortOrder = params.order || "desc";
|
||||
|
||||
const [data, totalCount] = await Promise.all([
|
||||
prisma.vendor.findMany({
|
||||
where,
|
||||
orderBy: { [sortField]: sortOrder },
|
||||
skip,
|
||||
take: perPage,
|
||||
include: {
|
||||
_count: { select: { filaments: true, resins: true, paints: true } },
|
||||
},
|
||||
}),
|
||||
prisma.vendor.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
pageCount: Math.ceil(totalCount / perPage),
|
||||
totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getVendorById(id: string, userId: string) {
|
||||
return prisma.vendor.findFirst({
|
||||
where: { id, userId },
|
||||
include: {
|
||||
_count: { select: { filaments: true, resins: true, paints: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getVendorOptions(userId: string) {
|
||||
return prisma.vendor.findMany({
|
||||
where: { userId, archived: false },
|
||||
select: { id: true, name: true },
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user