diff --git a/backend/src/db.js b/backend/src/db.js index 366e70a..f84abba 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -513,7 +513,7 @@ async function findPrimaryAdminUser() { return mapUserRow(rows[0]) } -async function listUsers({ queryText = '', sort = 'recent' } = {}) { +async function listUsers({ queryText = '', sort = 'recent', direction = 'desc' } = {}) { const where = [] const params = [] const trimmedQuery = typeof queryText === 'string' ? queryText.trim() : '' @@ -523,12 +523,19 @@ async function listUsers({ queryText = '', sort = 'recent' } = {}) { params.push(`%${trimmedQuery}%`, `%${trimmedQuery}%`) } + const isAsc = direction === 'asc' const orderBy = sort === 'created' - ? 'u.created_at DESC, recent_activity_at DESC, u.email ASC' + ? isAsc + ? 'u.created_at ASC, recent_activity_at ASC, u.email ASC' + : 'u.created_at DESC, recent_activity_at DESC, u.email ASC' : sort === 'tierlists' - ? 'tierlist_count DESC, recent_activity_at DESC, u.email ASC' - : 'recent_activity_at DESC, u.created_at ASC, u.email ASC' + ? isAsc + ? 'tierlist_count ASC, recent_activity_at ASC, u.email ASC' + : 'tierlist_count DESC, recent_activity_at DESC, u.email ASC' + : isAsc + ? 'recent_activity_at ASC, u.created_at ASC, u.email ASC' + : 'recent_activity_at DESC, u.created_at ASC, u.email ASC' const rows = await query( ` diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index 1306669..88e2065 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -565,12 +565,13 @@ router.get('/users', requireAdmin, async (req, res) => { const schema = z.object({ q: z.string().trim().max(120).optional().default(''), sort: z.enum(['recent', 'created', 'tierlists']).optional().default('recent'), + direction: z.enum(['asc', 'desc']).optional().default('desc'), }) const parsed = schema.safeParse(req.query) if (!parsed.success) return res.status(400).json({ error: 'bad_request' }) const [users, primaryAdmin] = await Promise.all([ - listUsers({ queryText: parsed.data.q, sort: parsed.data.sort }), + listUsers({ queryText: parsed.data.q, sort: parsed.data.sort, direction: parsed.data.direction }), findPrimaryAdminUser(), ]) res.json({ users: users.map((user) => decorateAdminUser(user, primaryAdmin)) }) diff --git a/docs/update.md b/docs/update.md index 997d786..986f99a 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,10 @@ # 업데이트 로그 +## 2026-04-01 v1.3.12 +- 관리자 회원 관리 상단에 정렬 방향 선택을 추가해, 최근 활동순·가입순·작성 티어표순을 각각 오름차순/내림차순으로 다시 볼 수 있게 확장함. +- 회원 정보 수정, 새 게임 생성, 비밀번호 초기화 모달은 Settings 톤 입력 스타일을 유지하면서 각 입력칸에 글자 수 피드백을 함께 보여주도록 정리함. +- 로그인, 설정, 티어 에디터 제목·설명·요청 제목·요청 설명·티어 행 이름에도 최대 길이와 현재 입력 길이 안내를 붙여, 제출 전에 제한을 바로 인지할 수 있게 개선함. + ## 2026-04-01 v1.3.11 - **회원 관리 편집 모달 전환**: 관리자 회원 카드를 읽기 전용 정보 카드로 바꾸고, `회원 정보 수정` 버튼으로 Settings 톤의 편집 모달에서 이메일/닉네임/운영자 권한을 저장하도록 재구성 - **회원 검색/정렬 추가**: 회원 관리 상단에 이메일/닉네임 검색과 `최근 활동순`, `가입순`, `작성 티어표 많은 순` 정렬을 추가해 운영자가 원하는 기준으로 목록을 다시 볼 수 있도록 확장 diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js index 3c9b4ba..e65be02 100644 --- a/frontend/src/lib/api.js +++ b/frontend/src/lib/api.js @@ -62,8 +62,8 @@ export const api = { approveAdminTemplateRequest: (requestId, payload) => request(`/api/admin/template-requests/${encodeURIComponent(requestId)}/approve`, { method: 'POST', body: payload || {} }), rejectAdminTemplateRequest: (requestId) => request(`/api/admin/template-requests/${encodeURIComponent(requestId)}/reject`, { method: 'POST', body: {} }), - listAdminUsers: ({ q = '', sort = 'recent' } = {}) => - request(`/api/admin/users?q=${encodeURIComponent(q)}&sort=${encodeURIComponent(sort)}`), + listAdminUsers: ({ q = '', sort = 'recent', direction = 'desc' } = {}) => + request(`/api/admin/users?q=${encodeURIComponent(q)}&sort=${encodeURIComponent(sort)}&direction=${encodeURIComponent(direction)}`), updateAdminUser: (userId, payload) => request(`/api/admin/users/${encodeURIComponent(userId)}`, { method: 'PATCH', body: payload }), updateAdminUserPassword: (userId, payload) => diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index eeb6076..cbae610 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -64,6 +64,7 @@ const modalTargetCustomItem = ref(null) const users = ref([]) const userQuery = ref('') const userSort = ref('recent') +const userSortDirection = ref('desc') const imageStats = ref(null) const imageQueue = ref({ concurrency: 1, activeCount: 0, pendingCount: 0 }) const imageRecentJobs = ref([]) @@ -515,7 +516,7 @@ async function refreshTemplateRequests() { async function refreshUsers() { if (!auth.user?.isAdmin) return try { - const data = await api.listAdminUsers({ q: userQuery.value, sort: userSort.value }) + const data = await api.listAdminUsers({ q: userQuery.value, sort: userSort.value, direction: userSortDirection.value }) users.value = (data.users || []).map((user) => ({ ...user, isAvatarBusy: false, @@ -1589,6 +1590,10 @@ async function saveFeaturedOrder() { + @@ -1658,8 +1663,22 @@ async function saveFeaturedOrder() {