Refactor requests, refactor users, add home dashboard

This commit is contained in:
2026-01-02 00:02:24 +01:00
parent 36fb2358dd
commit 4b308a5c21
20 changed files with 761 additions and 319 deletions

View File

@ -4,14 +4,13 @@ import { prisma } from "@/lib/prisma";
import {
commissionRequestTableRowSchema,
commissionStatusSchema,
} from "@/schemas/commissions/tableSchema";
} from "@/schemas/commissions/requests";
import { z } from "zod";
export type CursorPagination = { pageIndex: number; pageSize: number };
export type SortDir = "asc" | "desc";
const triStateSchema = z.enum(["any", "true", "false"]);
type TriState = z.infer<typeof triStateSchema>;
const sortingSchema = z.array(
z.object({
@ -42,7 +41,6 @@ export async function getCommissionRequestsTablePage(input: {
where.OR = [
{ customerName: { contains: q, mode: "insensitive" } },
{ customerEmail: { contains: q, mode: "insensitive" } },
{ message: { contains: q, mode: "insensitive" } },
];
}
}
@ -78,12 +76,12 @@ export async function getCommissionRequestsTablePage(input: {
take: pagination.pageSize,
select: {
id: true,
index: true,
createdAt: true,
status: true,
customerName: true,
customerEmail: true,
customerSocials: true,
message: true,
_count: { select: { files: true } },
},
}),
@ -91,13 +89,13 @@ export async function getCommissionRequestsTablePage(input: {
const mapped = rows.map((r) => ({
id: r.id,
index: r.index,
createdAt: r.createdAt.toISOString(),
status: r.status as any,
customerName: r.customerName,
customerEmail: r.customerEmail,
customerSocials: r.customerSocials ?? null,
messagePreview: r.message.slice(0, 140),
filesCount: r._count.files,
status: r.status as any,
fileCount: r._count.files,
}));
// Validate output once (helps catch schema drift)

View File

@ -1,7 +1,7 @@
"use server";
import { prisma } from "@/lib/prisma";
import { commissionStatusSchema } from "@/schemas/commissions/tableSchema";
import { commissionStatusSchema } from "@/schemas/commissions/requests";
import { z } from "zod";
export async function setCommissionRequestStatus(input: {

View File

@ -1,7 +1,7 @@
"use server";
import { prisma } from "@/lib/prisma";
import { commissionStatusSchema } from "@/schemas/commissions/tableSchema";
import { commissionStatusSchema } from "@/schemas/commissions/requests";
import { z } from "zod/v4";
const updateSchema = z.object({

View File

@ -1,23 +0,0 @@
"use server";
import { prisma } from "@/lib/prisma";
export async function deleteItems(itemId: string, type: string) {
switch (type) {
case "categories":
await prisma.artCategory.delete({ where: { id: itemId } });
break;
// case "tags":
// await prisma.artTag.delete({ where: { id: itemId } });
// break;
// case "types":
// await prisma.portfolioType.delete({ where: { id: itemId } });
// break;
// case "albums":
// await prisma.portfolioAlbum.delete({ where: { id: itemId } });
// break;
}
return { success: true };
}

View File

@ -0,0 +1,123 @@
"use server";
import { prisma } from "@/lib/prisma";
type CountRow<K extends string> = {
[P in K]: string;
} & { _count: { _all: number } };
function toCountMapSafe(rows: any[], key: string) {
const out: Record<string, number> = {};
for (const r of rows) out[String(r[key])] = Number(r?._count?._all ?? 0);
return out;
}
export async function getAdminDashboard() {
const now = new Date();
const days = (n: number) => new Date(now.getTime() - n * 24 * 60 * 60 * 1000);
const [
artworkTotal,
artworkPublished,
artworkNeedsWork,
artworkNsfw,
artworkHeader,
colorStatusRows,
recentArtworks,
commissionTotal,
commissionStatusRows,
commissionNew7d,
commissionNew30d,
recentRequests,
userTotal,
userUnverified,
userBanned,
] = await Promise.all([
prisma.artwork.count(),
prisma.artwork.count({ where: { published: true } }),
prisma.artwork.count({ where: { needsWork: true } }),
prisma.artwork.count({ where: { nsfw: true } }),
prisma.artwork.count({ where: { setAsHeader: true } }),
prisma.artwork.groupBy({
by: ["colorStatus"],
_count: { _all: true },
}),
prisma.artwork.findMany({
orderBy: { createdAt: "desc" },
take: 10,
select: {
id: true,
name: true,
slug: true,
createdAt: true,
published: true,
needsWork: true,
colorStatus: true,
},
}),
prisma.commissionRequest.count(),
prisma.commissionRequest.groupBy({
by: ["status"],
_count: { _all: true },
}),
prisma.commissionRequest.count({ where: { createdAt: { gte: days(7) } } }),
prisma.commissionRequest.count({ where: { createdAt: { gte: days(30) } } }),
prisma.commissionRequest.findMany({
orderBy: { createdAt: "desc" },
take: 10,
select: {
id: true,
createdAt: true,
status: true,
customerName: true,
customerEmail: true,
},
}),
prisma.user.count(),
prisma.user.count({ where: { emailVerified: false } }),
prisma.user.count({ where: { banned: true } }),
]);
const colorStatus = toCountMapSafe(colorStatusRows, "colorStatus");
const commissionStatus = toCountMapSafe(commissionStatusRows, "status");
return {
artworks: {
total: artworkTotal,
published: artworkPublished,
unpublished: artworkTotal - artworkPublished,
needsWork: artworkNeedsWork,
nsfw: artworkNsfw,
header: artworkHeader,
colorStatus: {
PENDING: colorStatus.PENDING ?? 0,
PROCESSING: colorStatus.PROCESSING ?? 0,
READY: colorStatus.READY ?? 0,
FAILED: colorStatus.FAILED ?? 0,
},
recent: recentArtworks,
},
commissions: {
total: commissionTotal,
status: {
NEW: commissionStatus.NEW ?? 0,
REVIEWING: commissionStatus.REVIEWING ?? 0,
ACCEPTED: commissionStatus.ACCEPTED ?? 0,
REJECTED: commissionStatus.REJECTED ?? 0,
SPAM: commissionStatus.SPAM ?? 0,
},
new7d: commissionNew7d,
new30d: commissionNew30d,
recent: recentRequests,
},
users: {
total: userTotal,
unverified: userUnverified,
banned: userBanned,
},
};
}