Phase 4. 마에스트로 목록 화면 구현
This commit is contained in:
+370
@@ -0,0 +1,370 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { db } from "@/lib/db";
|
||||
import type { Prisma } from "@/lib/generated/prisma/client";
|
||||
|
||||
const pageSizeOptions = [10, 20, 50, 100] as const;
|
||||
const sortOptions = [
|
||||
"id-desc",
|
||||
"id-asc",
|
||||
"joinedAt-desc",
|
||||
"joinedAt-asc",
|
||||
"availableAt-desc",
|
||||
"availableAt-asc",
|
||||
] as const;
|
||||
|
||||
const maestroListSearchSchema = z.object({
|
||||
page: z.coerce.number().int().min(1).catch(1),
|
||||
pageSize: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.refine((value) => pageSizeOptions.includes(value as PageSize))
|
||||
.catch(10),
|
||||
q: z.string().trim().catch(""),
|
||||
activateStatus: z
|
||||
.union([z.literal("all"), z.coerce.number().int().min(0)])
|
||||
.optional()
|
||||
.catch(undefined),
|
||||
accountType: z
|
||||
.union([z.literal("all"), z.coerce.number().int().min(0)])
|
||||
.optional()
|
||||
.catch(undefined),
|
||||
sort: z.enum(sortOptions).catch("id-desc"),
|
||||
});
|
||||
|
||||
type PageSize = (typeof pageSizeOptions)[number];
|
||||
export type MaestroSort = (typeof sortOptions)[number];
|
||||
export type MaestroListSearchParams = z.infer<typeof maestroListSearchSchema>;
|
||||
export type RawSearchParams =
|
||||
| URLSearchParams
|
||||
| Record<string, string | string[] | undefined>;
|
||||
|
||||
export type MaestroListItem = {
|
||||
maestroID: number;
|
||||
name: string;
|
||||
email: string;
|
||||
accountType: number;
|
||||
activateStatus: number;
|
||||
availableActivateDateTime: string;
|
||||
acceptClausesDateTime: string;
|
||||
playerCount: number;
|
||||
};
|
||||
|
||||
export type MaestroListResult = {
|
||||
items: MaestroListItem[];
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalCount: number;
|
||||
totalPages: number;
|
||||
q: string;
|
||||
activateStatus?: number | "all";
|
||||
accountType?: number | "all";
|
||||
sort: MaestroSort;
|
||||
};
|
||||
|
||||
export type MaestroDetail = {
|
||||
maestro: MaestroListItem & {
|
||||
allowEditEnterCode: number;
|
||||
maestroTestID: number | null;
|
||||
};
|
||||
counts: {
|
||||
players: number;
|
||||
extensionRequests: number;
|
||||
upgradeRequests: number;
|
||||
logs: number;
|
||||
};
|
||||
players: Array<{
|
||||
playerID: number;
|
||||
name: string;
|
||||
enterCode: string;
|
||||
accountType: number | null;
|
||||
}>;
|
||||
extensionRequests: Array<{
|
||||
maestroExtensionID: number;
|
||||
accountType: number;
|
||||
requestedDateTime: string;
|
||||
status: number;
|
||||
}>;
|
||||
upgradeRequests: Array<{
|
||||
maestroUpgradeID: number;
|
||||
registeredActivateStatus: number;
|
||||
registeredAccountType: number;
|
||||
requestedAccountType: number;
|
||||
requestedDateTime: string;
|
||||
status: number;
|
||||
}>;
|
||||
logs: Array<{
|
||||
maestroLogID: number;
|
||||
type: string;
|
||||
logDateTime: string;
|
||||
remark: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export async function getMaestros(
|
||||
rawSearchParams: RawSearchParams = {}
|
||||
): Promise<MaestroListResult> {
|
||||
const params = parseMaestroListSearchParams(rawSearchParams);
|
||||
const where = buildMaestroWhere(params);
|
||||
const orderBy = buildMaestroOrderBy(params.sort);
|
||||
const skip = (params.page - 1) * params.pageSize;
|
||||
|
||||
const [totalCount, maestros] = await Promise.all([
|
||||
db.maestro.count({ where }),
|
||||
db.maestro.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
skip,
|
||||
take: params.pageSize,
|
||||
select: {
|
||||
MaestroID: true,
|
||||
Name: true,
|
||||
Email: true,
|
||||
AccountType: true,
|
||||
ActivateStatus: true,
|
||||
AvailableActivateDateTime: true,
|
||||
AcceptClausesDateTime: true,
|
||||
PlayerCount: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(totalCount / params.pageSize));
|
||||
|
||||
return {
|
||||
items: maestros.map(toMaestroListItem),
|
||||
page: params.page,
|
||||
pageSize: params.pageSize,
|
||||
totalCount,
|
||||
totalPages,
|
||||
q: params.q,
|
||||
activateStatus: params.activateStatus,
|
||||
accountType: params.accountType,
|
||||
sort: params.sort,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getMaestroDetail(
|
||||
maestroID: number
|
||||
): Promise<MaestroDetail | null> {
|
||||
const maestro = await db.maestro.findUnique({
|
||||
where: { MaestroID: maestroID },
|
||||
select: {
|
||||
MaestroID: true,
|
||||
Name: true,
|
||||
Email: true,
|
||||
AccountType: true,
|
||||
ActivateStatus: true,
|
||||
AvailableActivateDateTime: true,
|
||||
AcceptClausesDateTime: true,
|
||||
PlayerCount: true,
|
||||
AllowEditEnterCode: true,
|
||||
MaestroTestID: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!maestro) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [
|
||||
playersCount,
|
||||
extensionRequestsCount,
|
||||
upgradeRequestsCount,
|
||||
logsCount,
|
||||
players,
|
||||
extensionRequests,
|
||||
upgradeRequests,
|
||||
logs,
|
||||
] = await Promise.all([
|
||||
db.player.count({ where: { MaestroID: maestroID } }),
|
||||
db.maestro_extension.count({ where: { MaestroID: maestroID } }),
|
||||
db.maestro_upgrade.count({ where: { MaestroID: maestroID } }),
|
||||
db.maestro_log.count({ where: { MaestroID: maestroID } }),
|
||||
db.player.findMany({
|
||||
where: { MaestroID: maestroID },
|
||||
orderBy: { PlayerID: "desc" },
|
||||
take: 20,
|
||||
select: {
|
||||
PlayerID: true,
|
||||
Name: true,
|
||||
EnterCode: true,
|
||||
AccountType: true,
|
||||
},
|
||||
}),
|
||||
db.maestro_extension.findMany({
|
||||
where: { MaestroID: maestroID },
|
||||
orderBy: { MaestroExtensionID: "desc" },
|
||||
take: 20,
|
||||
select: {
|
||||
MaestroExtensionID: true,
|
||||
AccountType: true,
|
||||
RequestedDateTime: true,
|
||||
Status: true,
|
||||
},
|
||||
}),
|
||||
db.maestro_upgrade.findMany({
|
||||
where: { MaestroID: maestroID },
|
||||
orderBy: { MaestroUpgradeID: "desc" },
|
||||
take: 20,
|
||||
select: {
|
||||
MaestroUpgradeID: true,
|
||||
RegisteredActivateStatus: true,
|
||||
RegisteredAccountType: true,
|
||||
RequestedAccountType: true,
|
||||
RequestedDateTime: true,
|
||||
Status: true,
|
||||
},
|
||||
}),
|
||||
db.maestro_log.findMany({
|
||||
where: { MaestroID: maestroID },
|
||||
orderBy: { MaestroLogID: "desc" },
|
||||
take: 30,
|
||||
select: {
|
||||
MaestroLogID: true,
|
||||
Type: true,
|
||||
LogDateTime: true,
|
||||
Remark: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
maestro: {
|
||||
...toMaestroListItem(maestro),
|
||||
allowEditEnterCode: maestro.AllowEditEnterCode,
|
||||
maestroTestID: maestro.MaestroTestID,
|
||||
},
|
||||
counts: {
|
||||
players: playersCount,
|
||||
extensionRequests: extensionRequestsCount,
|
||||
upgradeRequests: upgradeRequestsCount,
|
||||
logs: logsCount,
|
||||
},
|
||||
players: players.map((player) => ({
|
||||
playerID: player.PlayerID,
|
||||
name: player.Name.trim(),
|
||||
enterCode: player.EnterCode.trim(),
|
||||
accountType: player.AccountType,
|
||||
})),
|
||||
extensionRequests: extensionRequests.map((request) => ({
|
||||
maestroExtensionID: request.MaestroExtensionID,
|
||||
accountType: request.AccountType,
|
||||
requestedDateTime: request.RequestedDateTime.toISOString(),
|
||||
status: request.Status,
|
||||
})),
|
||||
upgradeRequests: upgradeRequests.map((request) => ({
|
||||
maestroUpgradeID: request.MaestroUpgradeID,
|
||||
registeredActivateStatus: request.RegisteredActivateStatus,
|
||||
registeredAccountType: request.RegisteredAccountType,
|
||||
requestedAccountType: request.RequestedAccountType,
|
||||
requestedDateTime: request.RequestedDateTime.toISOString(),
|
||||
status: request.Status,
|
||||
})),
|
||||
logs: logs.map((log) => ({
|
||||
maestroLogID: log.MaestroLogID,
|
||||
type: log.Type.trim(),
|
||||
logDateTime: log.LogDateTime.toISOString(),
|
||||
remark: log.Remark.trim(),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseMaestroListSearchParams(
|
||||
rawSearchParams: RawSearchParams
|
||||
): MaestroListSearchParams {
|
||||
return maestroListSearchSchema.parse(searchParamsToRecord(rawSearchParams));
|
||||
}
|
||||
|
||||
function buildMaestroWhere(
|
||||
params: MaestroListSearchParams
|
||||
): Prisma.maestroWhereInput {
|
||||
const conditions: Prisma.maestroWhereInput[] = [];
|
||||
|
||||
if (params.q) {
|
||||
const keywordConditions: Prisma.maestroWhereInput[] = [
|
||||
{ Name: { contains: params.q } },
|
||||
{ Email: { contains: params.q } },
|
||||
];
|
||||
const numericQuery = Number(params.q);
|
||||
|
||||
if (Number.isInteger(numericQuery) && numericQuery > 0) {
|
||||
keywordConditions.unshift({ MaestroID: numericQuery });
|
||||
}
|
||||
|
||||
conditions.push({ OR: keywordConditions });
|
||||
}
|
||||
|
||||
if (
|
||||
typeof params.activateStatus === "number" &&
|
||||
Number.isInteger(params.activateStatus)
|
||||
) {
|
||||
conditions.push({ ActivateStatus: params.activateStatus });
|
||||
}
|
||||
|
||||
if (
|
||||
typeof params.accountType === "number" &&
|
||||
Number.isInteger(params.accountType)
|
||||
) {
|
||||
conditions.push({ AccountType: params.accountType });
|
||||
}
|
||||
|
||||
return conditions.length > 0 ? { AND: conditions } : {};
|
||||
}
|
||||
|
||||
function buildMaestroOrderBy(
|
||||
sort: MaestroSort
|
||||
): Prisma.maestroOrderByWithRelationInput {
|
||||
switch (sort) {
|
||||
case "id-asc":
|
||||
return { MaestroID: "asc" };
|
||||
case "joinedAt-desc":
|
||||
return { AcceptClausesDateTime: "desc" };
|
||||
case "joinedAt-asc":
|
||||
return { AcceptClausesDateTime: "asc" };
|
||||
case "availableAt-desc":
|
||||
return { AvailableActivateDateTime: "desc" };
|
||||
case "availableAt-asc":
|
||||
return { AvailableActivateDateTime: "asc" };
|
||||
case "id-desc":
|
||||
default:
|
||||
return { MaestroID: "desc" };
|
||||
}
|
||||
}
|
||||
|
||||
function searchParamsToRecord(
|
||||
rawSearchParams: RawSearchParams
|
||||
): Record<string, string | undefined> {
|
||||
if (rawSearchParams instanceof URLSearchParams) {
|
||||
return Object.fromEntries(rawSearchParams.entries());
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(rawSearchParams).map(([key, value]) => [
|
||||
key,
|
||||
Array.isArray(value) ? value[0] : value,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
function toMaestroListItem(maestro: {
|
||||
MaestroID: number;
|
||||
Name: string;
|
||||
Email: string;
|
||||
AccountType: number;
|
||||
ActivateStatus: number;
|
||||
AvailableActivateDateTime: Date;
|
||||
AcceptClausesDateTime: Date;
|
||||
PlayerCount: number;
|
||||
}): MaestroListItem {
|
||||
return {
|
||||
maestroID: maestro.MaestroID,
|
||||
name: maestro.Name.trim(),
|
||||
email: maestro.Email.trim(),
|
||||
accountType: maestro.AccountType,
|
||||
activateStatus: maestro.ActivateStatus,
|
||||
availableActivateDateTime: maestro.AvailableActivateDateTime.toISOString(),
|
||||
acceptClausesDateTime: maestro.AcceptClausesDateTime.toISOString(),
|
||||
playerCount: maestro.PlayerCount,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user