Refactor requests, refactor users, add home dashboard
This commit is contained in:
@ -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)
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
123
src/actions/home/getDashboard.ts
Normal file
123
src/actions/home/getDashboard.ts
Normal 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user