임동욱 임동욱 09-03
feat: 관리자 회원 검색·페이징을 백엔드에 맡긴다
백엔드에 검색이 생겼다 — `searchCondition` 1=이름 2=아이디 3=전화번호로 매퍼가 컬럼을
고르고, 별도 카운트 쿼리로 전체 건수를 센다. 지금까지는 전체(최대 1만 건)를 받아
프론트에서 거르고 잘랐다.

이름순만 예외로 남긴다. 백엔드에 정렬 파라미터가 없어 그때는 여전히 전체를 받는다.

Co-Authored-By: Claude Opus 5 
@0e2100736b2fbd1b94e7a79feaccb7907ea9c88f
lib/data/repositories/admin-member-repository.ts
--- lib/data/repositories/admin-member-repository.ts
+++ lib/data/repositories/admin-member-repository.ts
@@ -10,8 +10,20 @@
 const ADMIN_MEMBER_BASE_PATH = '/api/v1/mngr/admin';
 const ADMIN_MEMBER_PAGINATION_PATH = `${ADMIN_MEMBER_BASE_PATH}/pagination`;
 
-// 이슈: 백엔드 `totalCount`가 전체가 아니라 현재 페이지 행 수라 페이징을 맡길 수 없다.
-// 이름순 정렬 파라미터도 없어 전체를 받아 서버에서 자른다.
+/**
+ * 검색 대상 → 백엔드 `searchCondition`. 매퍼가 이 값으로 컬럼을 고른다
+ * (1=USER_NM · 2=LOGIN_ID · 3=USER_TEL_NO).
+ */
+const SEARCH_CONDITION_BY_FIELD: Record<AdminMemberSearchField, string> = {
+  name: '1',
+  loginId: '2',
+  phoneNumber: '3',
+};
+
+/**
+ * 이름순 정렬에만 쓰는 상한. 백엔드에 정렬 파라미터가 없어 그때만 전체를 받아 여기서
+ * 정렬하고 자른다 — 검색·페이징은 백엔드가 한다.
+ */
 const ADMIN_MEMBER_FETCH_LIMIT = 10_000;
 
 function isRecord(value: unknown): value is Record<string, unknown> {
@@ -64,12 +76,26 @@
   };
 }
 
-async function fetchAllAdminMembers(): Promise<AdminMember[]> {
+/** 검색 조건을 백엔드 파라미터로 옮긴다. 검색어가 비면 조건 자체를 보내지 않는다. */
+function toSearchParams(query: AdminMemberQuery): Record<string, string> {
+  const keyword = query.keyword.trim();
+  if (!keyword) {
+    return {};
+  }
+  return {
+    searchCondition: SEARCH_CONDITION_BY_FIELD[query.searchField],
+    searchKeyword: keyword,
+  };
+}
+
+async function requestPage(
+  params: Record<string, string | number>
+): Promise<{ items: AdminMember[]; totalCount: number }> {
   const accessToken = await getSessionAccessToken();
 
   const result = await backendFetch<unknown>(ADMIN_MEMBER_PAGINATION_PATH, {
     method: 'GET',
-    query: { pageIndex: 1, recordCountPerPage: ADMIN_MEMBER_FETCH_LIMIT },
+    query: params,
     accessToken: accessToken ?? undefined,
     cache: 'no-store',
   });
@@ -83,29 +109,10 @@
     throw new Error('관리자 회원 목록 응답의 형식이 올바르지 않습니다.');
   }
 
-  return data.list.map(toAdminMember);
-}
+  const totalCount =
+    typeof data.totalCount === 'number' ? data.totalCount : data.list.length;
 
-const SEARCH_VALUE_BY_FIELD: Record<
-  AdminMemberSearchField,
-  (member: AdminMember) => string
-> = {
-  name: (member) => member.name,
-  loginId: (member) => member.loginId,
-  phoneNumber: (member) => member.phoneNumber ?? '',
-};
-
-function filterByKeyword(
-  items: AdminMember[],
-  query: AdminMemberQuery
-): AdminMember[] {
-  const keyword = query.keyword.trim().toLowerCase();
-  if (!keyword) {
-    return items;
-  }
-
-  const readValue = SEARCH_VALUE_BY_FIELD[query.searchField];
-  return items.filter((item) => readValue(item).toLowerCase().includes(keyword));
+  return { items: data.list.map(toAdminMember), totalCount };
 }
 
 function sortItems(
@@ -123,18 +130,37 @@
   totalCount: number;
 };
 
+/**
+ * 검색·페이징은 백엔드가 한다 — 매퍼가 `searchCondition`으로 컬럼을 고르고 별도 카운트
+ * 쿼리로 전체 건수를 센다.
+ *
+ * **이름순만 예외다.** 백엔드에 정렬 파라미터가 없어 그때는 전체를 받아 여기서 정렬하고
+ * 자른다. 정렬이 붙으면 이 갈래를 지우면 된다.
+ */
 export async function fetchAdminMembers(
   query: AdminMemberQuery
 ): Promise<AdminMemberPage> {
-  const all = await fetchAllAdminMembers();
-  const matched = sortItems(filterByKeyword(all, query), query);
+  const search = toSearchParams(query);
 
-  const offset = (query.page - 1) * query.pageSize;
+  if (query.sort === 'name') {
+    const { items } = await requestPage({
+      pageIndex: 1,
+      recordCountPerPage: ADMIN_MEMBER_FETCH_LIMIT,
+      ...search,
+    });
+    const sorted = sortItems(items, query);
+    const offset = (query.page - 1) * query.pageSize;
+    return {
+      items: sorted.slice(offset, offset + query.pageSize),
+      totalCount: sorted.length,
+    };
+  }
 
-  return {
-    items: matched.slice(offset, offset + query.pageSize),
-    totalCount: matched.length,
-  };
+  return requestPage({
+    pageIndex: query.page,
+    recordCountPerPage: query.pageSize,
+    ...search,
+  });
 }
 
 export async function findAdminMemberById(
Add a comment
List