From cc373f53fec41a3669b943b50ac098cb4184d5aa Mon Sep 17 00:00:00 2001 From: koreacomp5 Date: Sun, 2 Nov 2025 04:39:23 +0900 Subject: [PATCH] sub --- .../migration.sql | 15 +++ prisma/schema.prisma | 4 + src/app/admin/users/page.tsx | 115 +++++++++++++----- src/app/api/admin/users/route.ts | 60 +++++---- src/app/boards/[id]/page.tsx | 57 ++++++++- 5 files changed, 188 insertions(+), 63 deletions(-) create mode 100644 prisma/migrations/20251101170000_add_user_points_level_grade/migration.sql diff --git a/prisma/migrations/20251101170000_add_user_points_level_grade/migration.sql b/prisma/migrations/20251101170000_add_user_points_level_grade/migration.sql new file mode 100644 index 0000000..09c0acf --- /dev/null +++ b/prisma/migrations/20251101170000_add_user_points_level_grade/migration.sql @@ -0,0 +1,15 @@ +-- Add points/level/grade columns to users +PRAGMA foreign_keys=OFF; + +-- points +ALTER TABLE "users" ADD COLUMN "points" INTEGER NOT NULL DEFAULT 0; + +-- level +ALTER TABLE "users" ADD COLUMN "level" INTEGER NOT NULL DEFAULT 1; + +-- grade (0~10 권장, DB 레벨에서는 정수 제약만, 범위는 앱에서 검증) +ALTER TABLE "users" ADD COLUMN "grade" INTEGER NOT NULL DEFAULT 0; + +PRAGMA foreign_keys=ON; + + diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d511eb0..e6d99ec 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -233,6 +233,10 @@ model User { birth DateTime phone String @unique rank Int @default(0) + // 누적 포인트, 레벨, 등급(0~10) + points Int @default(0) + level Int @default(1) + grade Int @default(0) status UserStatus @default(active) authLevel AuthLevel @default(USER) diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index 0aeafba..2d99278 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -1,36 +1,80 @@ "use client"; import useSWR from "swr"; -import { useState } from "react"; +import { useMemo, useState } from "react"; const fetcher = (url: string) => fetch(url).then((r) => r.json()); export default function AdminUsersPage() { const [q, setQ] = useState(""); - const { data, mutate } = useSWR<{ users: any[] }>(`/api/admin/users?q=${encodeURIComponent(q)}`, fetcher); + const [queryDraft, setQueryDraft] = useState(""); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const key = useMemo(() => ( + `/api/admin/users?q=${encodeURIComponent(q)}&page=${page}&pageSize=${pageSize}` + ),[q, page, pageSize]); + const { data, mutate } = useSWR<{ total: number; page: number; pageSize: number; users: any[] }>(key, fetcher); const users = data?.users ?? []; + const total = data?.total ?? 0; + const totalPages = Math.max(1, Math.ceil(total / pageSize)); + const handleSearch = () => { + const term = queryDraft.trim(); + if (!term) { setQ(""); setPage(1); return; } + setQ(term); + setPage(1); + }; return (

사용자 관리

-
- setQ(e.target.value)} /> +
+ setQueryDraft(e.target.value)} + onKeyDown={(e) => { if (e.key === "Enter") handleSearch(); }} + /> + + 총 {total.toLocaleString()}명 +
+ +
+ + + + + + + + + + + + + + + + {users.map((u) => ( + + ))} + +
닉네임이름전화포인트레벨등급상태권한작업
+
+ +
+ + 페이지 {page} / {totalPages} + + 페이지 크기 +
- - - - - - - - - - - - - {users.map((u) => ( - - ))} - -
닉네임이름전화상태권한
); } @@ -45,25 +89,30 @@ function Row({ u, onChanged }: { u: any; onChanged: () => void }) { } const allRoles = ["admin", "editor", "user"] as const; return ( - - {u.nickname} - {u.name} - {u.phone} - - setStatus(e.target.value)}> + + + - + {allRoles.map((r) => ( -