임동욱 임동욱 09-22
feat: 1:1문의 목록에 질문내용·질문자명 검색 상자를 둔다
1:1문의는 앱이 제목(bbsNm)에 유형 이름만 넣어 백엔드 검색(BBS_NM LIKE)이
쓸모없다. 검색 상자가 없던 이유가 그것이라, 백엔드가 못 거르는 두 대상은
Repository가 전 페이지(500건씩·최대 20쪽)를 받아 걸러 페이지로 자른다.
검색어가 없으면 종전처럼 서버 페이징을 쓴다.

백엔드에 searchCondition 2(BBS_CN)·3(작성자)이 생기면 서버 검색으로 바꾼다.

Co-Authored-By: Claude Opus 5 
@0af7c0dc127dee39474ca6999d0e28ad51b62e15
lib/data/repositories/board-post-repository.ts
--- lib/data/repositories/board-post-repository.ts
+++ lib/data/repositories/board-post-repository.ts
@@ -141,6 +141,35 @@
   totalCount: number;
 };
 
+// 질문내용·질문자명 검색은 백엔드에 조건이 없어 전 페이지를 받아 거른다. 상한을 넘으면 그 뒤는 잘린다.
+// TODO(백엔드): MngrBbsMapper에 searchCondition 2(BBS_CN)·3(작성자)이 생기면 서버 검색으로 바꾼다.
+const SCAN_PAGE_SIZE = 500;
+const MAX_SCAN_PAGES = 20;
+
+async function fetchAllBoardPosts(
+  boardType: BoardType,
+  params: Record<string, string | number>
+): Promise<BoardPost[]> {
+  const items: BoardPost[] = [];
+  for (let pageIndex = 1; pageIndex <= MAX_SCAN_PAGES; pageIndex += 1) {
+    const page = await requestBoardPage(boardType, {
+      ...params,
+      pageIndex,
+      recordCountPerPage: SCAN_PAGE_SIZE,
+    });
+    items.push(...page.items);
+    if (page.items.length < SCAN_PAGE_SIZE || items.length >= page.totalCount) break;
+  }
+  return items;
+}
+
+function matchesLocalSearch(post: BoardPost, field: string, keyword: string): boolean {
+  const needle = keyword.toLowerCase();
+  if (field === 'content') return post.content.toLowerCase().includes(needle);
+  if (field === 'author') return (post.authorName ?? '').toLowerCase().includes(needle);
+  return true;
+}
+
 /**
  * 검색·필터·페이징이 적용된 목록을 조회한다.
  *
@@ -151,10 +180,7 @@
   boardType: BoardType,
   query: BoardPostQuery
 ): Promise<BoardPostPage> {
-  const params: Record<string, string | number> = {
-    pageIndex: query.page,
-    recordCountPerPage: query.pageSize,
-  };
+  const params: Record<string, string | number> = {};
 
   // 백엔드가 거를 수 있는 둘만 넘긴다 — 제목(searchCondition=1)과 노출여부(RLS_YN).
   // 나머지 조건은 화면에서 감췄다(`BOARD_SEARCH_FIELDS`·`BOARD_QUERY_FIELDS`).
@@ -169,7 +195,21 @@
     params.searchUseYn = 'N';
   }
 
-  return requestBoardPage(boardType, params);
+  // 백엔드가 못 거르는 대상(질문내용·질문자명)은 전부 받아 여기서 거르고 페이지로 자른다.
+  const isLocalSearch = keyword !== '' && (query.searchField === 'content' || query.searchField === 'author');
+  if (isLocalSearch) {
+    const matched = (await fetchAllBoardPosts(boardType, params)).filter((post) =>
+      matchesLocalSearch(post, query.searchField, keyword)
+    );
+    const start = (query.page - 1) * query.pageSize;
+    return { items: matched.slice(start, start + query.pageSize), totalCount: matched.length };
+  }
+
+  return requestBoardPage(boardType, {
+    ...params,
+    pageIndex: query.page,
+    recordCountPerPage: query.pageSize,
+  });
 }
 
 /** 단건 조회 — 수정·답변 팝업의 진입점. */
lib/domain/board-post-query.ts
--- lib/domain/board-post-query.ts
+++ lib/domain/board-post-query.ts
@@ -46,8 +46,12 @@
       : []),
     { value: 'title', label: '제목' },
   ],
-  // 1:1문의는 제목 없이 내용만 쓴다 — 백엔드가 내용을 못 걸러 남는 대상이 없다.
-  inquiry: [],
+  // 1:1문의는 제목(bbsNm)에 유형 이름만 들어 있어 백엔드 검색(제목 LIKE)이 쓸모없다 — 질문내용·
+  // 질문자명은 Repository가 전 페이지를 받아 거른다(백엔드에 조건이 생기면 서버 검색으로 바꾼다).
+  inquiry: [
+    { value: 'content', label: '질문내용' },
+    { value: 'author', label: '질문자명' },
+  ],
   faq: [{ value: 'title', label: '제목' }],
 };
 
Add a comment
List