임동욱 임동욱 09-18
fix: 가맹몰 상세 목록 검색·행 버튼·메뉴 설명 줄바꿈·메뉴 화면 업체명을 고친다
- 상단 업체명 검색이 안 됐다 — 목록 컨테이너의 검색 상자는 onQueryChange로만 알리는데 링크만
  넘기고 있었다. 백엔드 조건(searchFrcsNm)은 있으나 목록을 합쳐 프론트에서 거르므로 그대로 쓴다.
- 상세가 없는 행의 [비활성]·[삭제]를 잠근다.
- 메뉴 설명의 줄바꿈을 표에서 살린다(pre-line, 3줄 뒤 말줄임).
- 단건 조회가 업체명 대신 코드를 줄 때 가맹점 목록에서 채우는 것을 Repository로 옮겨 메뉴
  등록·수정 화면의 「대상 업체」에도 이름이 나온다.

Co-Authored-By: Claude Fable 5.1 
@26fc7680f7506c8984044084739fe98e30b3e9cf
app/(protected)/(basic)/system/merchant-details/[frcsNo]/page.tsx
--- app/(protected)/(basic)/system/merchant-details/[frcsNo]/page.tsx
+++ app/(protected)/(basic)/system/merchant-details/[frcsNo]/page.tsx
@@ -36,16 +36,14 @@
     return <MerchantDetailForm merchant={merchant} categories={await categoriesPromise} />;
   }
 
-  const [products, categories, badgeCodes, merchant] = await Promise.all([
+  const [products, categories, badgeCodes] = await Promise.all([
     fetchMerchantProducts(frcsNo),
     categoriesPromise,
     fetchCommonCodes(CODE_GROUP.merchantProductBadge),
-    // 상세 응답에 업체명이 빠지는 백엔드 결함 — 코드만 왔으면 가맹점 목록에서 이름을 채운다.
-    detail.name === detail.id ? fetchMerchantSummary(frcsNo) : Promise.resolve(null),
   ]);
   return (
     <MerchantDetailForm
-      detail={merchant ? { ...detail, name: merchant.name } : detail}
+      detail={detail}
       products={products}
       categories={categories}
       badgeCodes={badgeCodes}
app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-list.tsx
--- app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-list.tsx
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-list.tsx
@@ -382,6 +382,10 @@
           ...(patch.page !== undefined ? { page: patch.page } : {}),
         })
       }
+      // 검색 상자는 링크가 아니라 콜백으로만 알린다(FoxListSearch.onSearch) — 페이지는 위 링크가 맡는다.
+      onQueryChange={(patch) => {
+        if (patch.keyword !== undefined) go({ keyword: String(patch.keyword) });
+      }}
     />
   );
 }
app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-row-actions.tsx
--- app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-row-actions.tsx
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-row-actions.tsx
@@ -18,8 +18,8 @@
 
 /**
  * 기획 BO-MAL-001 관리 열 — 모든 행에 [수정] [비활성|노출] [삭제](시안대로, 사용자 확정). [수정]은 그
- * 가맹점의 상세 화면으로 가고(상세가 없으면 거기서 처음 만든다), 상세가 없는 행의 [비활성]·[삭제]는
- * 백엔드가 NOT_FOUND로 거절해 사유가 토스트로 올라온다.
+ * 가맹점의 상세 화면으로 가고(상세가 없으면 거기서 처음 만든다), 상세가 없는 행은 [비활성]·[삭제]가
+ * 잠긴다 — 지울 상세가 없기 때문이다.
  */
 export function MerchantDetailRowActions({ row }: MerchantDetailRowActionsProps) {
   const router = useRouter();
@@ -76,14 +76,14 @@
         type="default"
         size="sm"
         label={row.isVisible === false ? '노출' : '비활성'}
-        disabled={isBusy}
+        disabled={isBusy || !row.hasDetail}
         onAction={toggleVisibility}
       />
       <FoxButton
         type="default"
         size="sm"
         label="삭제"
-        disabled={isBusy}
+        disabled={isBusy || !row.hasDetail}
         onAction={confirmDelete}
       />
     </FoxButtonGroup>
app/(protected)/(basic)/system/merchant-details/_components/merchant-product-table.module.scss
--- app/(protected)/(basic)/system/merchant-details/_components/merchant-product-table.module.scss
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-product-table.module.scss
@@ -29,11 +29,13 @@
   overflow-x: auto;
 }
 
+// 입력한 줄바꿈을 살린다(기획 BO-MAL-004 ⑤ 2~3줄). 넘치면 말줄임.
 .description {
   display: -webkit-box;
   overflow: hidden;
   -webkit-box-orient: vertical;
-  -webkit-line-clamp: 2;
+  -webkit-line-clamp: 3;
+  white-space: pre-line;
 }
 
 .badges {
lib/data/repositories/merchant-detail-repository.ts
--- lib/data/repositories/merchant-detail-repository.ts
+++ lib/data/repositories/merchant-detail-repository.ts
@@ -254,7 +254,13 @@
     if (result.code === 404) return null;
     throw new BackendRequestError(result);
   }
-  return toDetail(result.data, withImages);
+  const detail = await toDetail(result.data, withImages);
+  // 결함 1 — 응답에 업체명이 없어 코드만 왔으면 가맹점 목록에서 이름을 채운다.
+  if (detail && detail.name === detail.id) {
+    const merchant = await fetchMerchantSummary(frcsNo);
+    if (merchant) return { ...detail, name: merchant.name };
+  }
+  return detail;
 }
 
 async function fetchPage<T>(
Add a comment
List