From a743a6fa2bb1d475c0313aad57a6c515351e00b3 Mon Sep 17 00:00:00 2001 From: yhaeul Date: Mon, 10 Aug 2026 15:11:04 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=9A=B4=EC=98=81=EC=A7=84=20?= =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=EC=97=90=20=EC=A4=91?= =?UTF-8?q?=EC=95=99=EB=8F=99=EC=95=84=EB=A6=AC=20=EC=A0=9C=EC=99=B8=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/server/service/admin-club.service.ts | 24 ++++++++++++++++++- apps/web/src/admin/api.ts | 14 ++++++----- apps/web/src/admin/constants.ts | 1 + apps/web/src/admin/types.ts | 2 +- apps/web/src/lib/schemas/admin.ts | 4 ++++ 5 files changed, 37 insertions(+), 8 deletions(-) diff --git a/apps/web/server/service/admin-club.service.ts b/apps/web/server/service/admin-club.service.ts index c641d32a..6fd6e6ec 100644 --- a/apps/web/server/service/admin-club.service.ts +++ b/apps/web/server/service/admin-club.service.ts @@ -14,7 +14,7 @@ import type { AdminClubVerificationRequestStatusUpdate, AdminClubVerificationRequestsQuery, } from 'src/lib/schemas/admin' -import { type FindOptionsWhere, In, IsNull, type Repository } from 'typeorm' +import { type FindOptionsWhere, In, IsNull, Not, type Repository } from 'typeorm' import { ClubEntity, ClubHistoryEntity, @@ -139,6 +139,7 @@ export class AdminClubService { async getAdminClubs({ status, affiliation_type: affiliationType, + exclude_affiliation_type: excludeAffiliationType, offset, limit, }: AdminClubsQuery): Promise> { @@ -151,6 +152,9 @@ export class AdminClubService { if (affiliationType) { where.affiliationType = affiliationType } + if (excludeAffiliationType) { + where.affiliationType = Not(excludeAffiliationType) + } const [clubs, totalCount] = await this.clubRepository.findAndCount({ where, @@ -320,6 +324,7 @@ export class AdminClubService { club_uuid: clubUuid, query, affiliation_type: affiliationType, + exclude_affiliation_type: excludeAffiliationType, offset, limit, }: AdminClubHistoriesQuery): Promise<{ total_count: number; histories: AdminClubHistoryItem[] }> { @@ -342,6 +347,11 @@ export class AdminClubService { if (affiliationType) { baseQuery.andWhere('club.affiliation_type = :affiliationType', { affiliationType }) } + if (excludeAffiliationType) { + baseQuery.andWhere('club.affiliation_type != :excludeAffiliationType', { + excludeAffiliationType, + }) + } if (trimmedQuery) { baseQuery.andWhere( @@ -403,6 +413,7 @@ export class AdminClubService { async getAdminClubManagerRequests({ status, affiliation_type: affiliationType, + exclude_affiliation_type: excludeAffiliationType, offset, limit, }: AdminClubManagerRequestsQuery): Promise< @@ -418,6 +429,11 @@ export class AdminClubService { if (affiliationType) { baseQuery.andWhere('club.affiliation_type = :affiliationType', { affiliationType }) } + if (excludeAffiliationType) { + baseQuery.andWhere('club.affiliation_type != :excludeAffiliationType', { + excludeAffiliationType, + }) + } const totalCount = await baseQuery.getCount() const requests = await baseQuery @@ -476,6 +492,7 @@ export class AdminClubService { async getAdminClubVerificationRequests({ status, affiliation_type: affiliationType, + exclude_affiliation_type: excludeAffiliationType, offset, limit, }: AdminClubVerificationRequestsQuery): Promise< @@ -491,6 +508,11 @@ export class AdminClubService { if (affiliationType) { baseQuery.andWhere('club.affiliation_type = :affiliationType', { affiliationType }) } + if (excludeAffiliationType) { + baseQuery.andWhere('club.affiliation_type != :excludeAffiliationType', { + excludeAffiliationType, + }) + } const totalCount = await baseQuery.getCount() const requests = await baseQuery diff --git a/apps/web/src/admin/api.ts b/apps/web/src/admin/api.ts index c3161a43..3f5eeaea 100644 --- a/apps/web/src/admin/api.ts +++ b/apps/web/src/admin/api.ts @@ -46,8 +46,10 @@ export const verifyAdminRole = async (token: string): Promise => { if (!res.ok) throw new Error('admin role required') } -const getAffiliationType = (affiliationFilter: AffiliationFilter) => - affiliationFilter === 'CENTRAL' ? '중앙동아리' : undefined +const getAffiliationFilterParams = (affiliationFilter: AffiliationFilter) => ({ + affiliation_type: affiliationFilter === 'CENTRAL' ? '중앙동아리' : undefined, + exclude_affiliation_type: affiliationFilter === 'NON_CENTRAL' ? '중앙동아리' : undefined, +}) export const fetchClubs = ( status: StatusFilter, @@ -57,7 +59,7 @@ export const fetchClubs = ( request( `/api/v2/admin/clubs${buildQuery({ status: status === 'ALL' ? undefined : status, - affiliation_type: getAffiliationType(affiliationFilter), + ...getAffiliationFilterParams(affiliationFilter), ...pagination, })}`, ) @@ -73,7 +75,7 @@ export const fetchManagerRequests = ( request( `/api/v2/admin/clubs/manager-requests${buildQuery({ status: status === 'ALL' ? undefined : status, - affiliation_type: getAffiliationType(affiliationFilter), + ...getAffiliationFilterParams(affiliationFilter), ...pagination, })}`, ) @@ -86,7 +88,7 @@ export const fetchVerificationRequests = ( request( `/api/v2/admin/clubs/verifications${buildQuery({ status: status === 'ALL' ? undefined : status, - affiliation_type: getAffiliationType(affiliationFilter), + ...getAffiliationFilterParams(affiliationFilter), ...pagination, })}`, ) @@ -99,7 +101,7 @@ export const fetchHistories = ( request( `/api/v2/admin/clubs/histories${buildQuery({ query, - affiliation_type: getAffiliationType(affiliationFilter), + ...getAffiliationFilterParams(affiliationFilter), ...pagination, })}`, ) diff --git a/apps/web/src/admin/constants.ts b/apps/web/src/admin/constants.ts index 55383373..0004f121 100644 --- a/apps/web/src/admin/constants.ts +++ b/apps/web/src/admin/constants.ts @@ -14,6 +14,7 @@ export const STATUS_FILTERS: { label: string; value: StatusFilter }[] = [ export const AFFILIATION_FILTERS: { label: string; value: AffiliationFilter }[] = [ { label: '전체', value: 'ALL' }, { label: '중앙동아리', value: 'CENTRAL' }, + { label: '중앙동아리 제외', value: 'NON_CENTRAL' }, ] export const TABS: { label: string; value: AdminTab }[] = [ diff --git a/apps/web/src/admin/types.ts b/apps/web/src/admin/types.ts index cc6b3eb1..c9082fdc 100644 --- a/apps/web/src/admin/types.ts +++ b/apps/web/src/admin/types.ts @@ -10,7 +10,7 @@ import type { export type { ClubDecisionStatus as DecisionStatus, ClubStatus } export type StatusFilter = ClubStatus | 'ALL' -export type AffiliationFilter = 'ALL' | 'CENTRAL' +export type AffiliationFilter = 'ALL' | 'CENTRAL' | 'NON_CENTRAL' export type AdminTab = 'clubs' | 'managerRequests' | 'verificationRequests' | 'histories' export type AdminClub = AdminClubsResponse['data']['clubs'][number] diff --git a/apps/web/src/lib/schemas/admin.ts b/apps/web/src/lib/schemas/admin.ts index 7f935557..4dd02eaf 100644 --- a/apps/web/src/lib/schemas/admin.ts +++ b/apps/web/src/lib/schemas/admin.ts @@ -31,6 +31,7 @@ export const AdminClubsQuerySchema = z .object({ status: z.enum(CLUB_STATUSES).optional(), affiliation_type: z.enum(ADMIN_CLUB_AFFILIATION_TYPES).optional(), + exclude_affiliation_type: z.enum(ADMIN_CLUB_AFFILIATION_TYPES).optional(), ...AdminPaginationQueryFields, }) .openapi('AdminClubsQuery') @@ -140,6 +141,7 @@ export const AdminClubHistoriesQuerySchema = z club_uuid: z.string().uuid().optional(), query: z.string().trim().optional(), affiliation_type: z.enum(ADMIN_CLUB_AFFILIATION_TYPES).optional(), + exclude_affiliation_type: z.enum(ADMIN_CLUB_AFFILIATION_TYPES).optional(), ...AdminPaginationQueryFields, }) .openapi('AdminClubHistoriesQuery') @@ -181,6 +183,7 @@ export const AdminClubManagerRequestsQuerySchema = z .object({ status: z.enum(CLUB_STATUSES).optional(), affiliation_type: z.enum(ADMIN_CLUB_AFFILIATION_TYPES).optional(), + exclude_affiliation_type: z.enum(ADMIN_CLUB_AFFILIATION_TYPES).optional(), ...AdminPaginationQueryFields, }) .openapi('AdminClubManagerRequestsQuery') @@ -224,6 +227,7 @@ export const AdminClubVerificationRequestsQuerySchema = z .object({ status: z.enum(CLUB_STATUSES).optional(), affiliation_type: z.enum(ADMIN_CLUB_AFFILIATION_TYPES).optional(), + exclude_affiliation_type: z.enum(ADMIN_CLUB_AFFILIATION_TYPES).optional(), ...AdminPaginationQueryFields, }) .openapi('AdminClubVerificationRequestsQuery') From aeda80dcd6e15d2429b368c00f0e94b3e4e5cd73 Mon Sep 17 00:00:00 2001 From: yhaeul Date: Mon, 10 Aug 2026 15:31:21 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9A=94=EC=B2=AD=20=EA=B4=80=EB=A6=AC=20UI=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/pages/admin/index.tsx | 3 +- apps/web/src/admin/components/AdminLayout.tsx | 35 +++++++++++++------ apps/web/src/admin/components/ClubsTab.tsx | 2 +- .../admin/components/ManagerRequestsTab.tsx | 3 +- apps/web/src/admin/components/ui.tsx | 2 +- apps/web/src/admin/constants.ts | 6 ++-- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/apps/web/pages/admin/index.tsx b/apps/web/pages/admin/index.tsx index fe465c4f..c36f2a7b 100644 --- a/apps/web/pages/admin/index.tsx +++ b/apps/web/pages/admin/index.tsx @@ -56,9 +56,7 @@ const AdminDashboardPage = () => { { statusFilter={statusFilter} onStatusChange={setStatusFilter} pendingCount={pendingCounts[activeTab as keyof typeof pendingCounts] ?? 0} + totalCount={totalCount} /> {activeTab === 'clubs' && ( void - totalCount: number pendingCounts: PendingCounts - statusFilter: StatusFilter affiliationFilter: AffiliationFilter onAffiliationFilterChange: (filter: AffiliationFilter) => void onLogout: () => void @@ -109,10 +105,6 @@ export const AdminLayout = ({
- - - 총 {totalCount.toLocaleString()}건 -