임동욱 임동욱 09-11
feat: 가맹몰 상세 목록에 필터 초기화를 두고 행 선택을 모든 행에서 살린다
초기화는 카테고리·노출 상태·기간·업체명을 모두 비우고 첫 페이지로 돌아간다(초안과 URL을
함께 되돌린다).

행 선택 체크박스가 상세가 있는 행에만 그려져, 상세가 하나도 없는 목록에서는 기능이 통째로
사라졌다(사용자 지적). 모든 행에서 고를 수 있게 하고, 일괄 비활성·삭제는 그중 상세가 있는
행만 처리한다 — 없는 행은 백엔드가 NOT_FOUND로 거절하므로 보내기 전에 거른다.

Co-Authored-By: Claude Opus 5 
@f87bb1bdf80adc7fa0b12a8f4f02c0b9bcb9de92
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
@@ -25,6 +25,7 @@
 } from '@/lib/domain/merchant-detail';
 import {
   FILTER_ALL,
+  MERCHANT_DETAILS_PATH,
   MERCHANT_DETAIL_PAGE_SIZE_OPTIONS,
   buildMerchantDetailHref,
   clampDateRange,
@@ -79,9 +80,8 @@
     endDate: query.endDate,
   });
 
-  const selectableIds = items.filter((row) => row.hasDetail).map((row) => row.id);
-  const isAllSelected =
-    selectableIds.length > 0 && selectableIds.every((id) => selectedIds.has(id));
+  const allIds = items.map((row) => row.id);
+  const isAllSelected = allIds.length > 0 && allIds.every((id) => selectedIds.has(id));
 
   function go(patch: Partial<MerchantDetailQuery>) {
     router.replace(buildMerchantDetailHref(query, { ...patch, page: 1 }));
@@ -93,7 +93,13 @@
   }
 
   function toggleAll(checked: boolean) {
-    setSelectedIds(checked ? new Set(selectableIds) : new Set());
+    setSelectedIds(checked ? new Set(allIds) : new Set());
+  }
+
+  /** 모든 필터를 비우고 첫 페이지로 — 초안과 URL을 함께 되돌린다. */
+  function resetFilters() {
+    setDraft({ category: FILTER_ALL, visibility: FILTER_ALL, startDate: '', endDate: '' });
+    router.replace(MERCHANT_DETAILS_PATH);
   }
 
   function toggleOne(id: string, checked: boolean) {
@@ -110,7 +116,13 @@
     successMessage: string
   ) {
     hideAlert();
-    const ids = [...selectedIds];
+    const ids = items
+      .filter((row) => selectedIds.has(row.id) && row.hasDetail)
+      .map((row) => row.id);
+    if (ids.length === 0) {
+      showToast({ variant: 'warning', message: '상세가 등록된 행만 처리할 수 있습니다.' });
+      return;
+    }
     startAction(async () => {
       const result = await run(ids);
       showToast(
@@ -122,11 +134,15 @@
     });
   }
 
+  const selectedDetailCount = items.filter(
+    (row) => selectedIds.has(row.id) && row.hasDetail
+  ).length;
+
   function confirmBulkDelete() {
     showAlert({
       variant: 'danger',
       title: '선택한 상세를 삭제하시겠습니까?',
-      message: `${selectedIds.size}건의 상세 정보를 삭제합니다. 삭제 후에는 되돌릴 수 없습니다.`,
+      message: `${selectedDetailCount}건의 상세 정보를 삭제합니다. 삭제 후에는 되돌릴 수 없습니다.`,
       actions: (
         <FoxButtonGroup size="md">
           <FoxButton type="default" size="md" label="취소" onAction={hideAlert} />
@@ -150,22 +166,21 @@
           label="전체 선택"
           hideLabel
           checked={isAllSelected}
-          disabled={selectableIds.length === 0}
+          disabled={allIds.length === 0}
           onChange={toggleAll}
         />
       ),
       width: 56,
       align: 'center',
-      render: (row) =>
-        row.hasDetail ? (
-          <FoxCheckbox
-            size="md"
-            label={`${row.name} 선택`}
-            hideLabel
-            checked={selectedIds.has(row.id)}
-            onChange={(checked) => toggleOne(row.id, checked)}
-          />
-        ) : null,
+      render: (row) => (
+        <FoxCheckbox
+          size="md"
+          label={`${row.name} 선택`}
+          hideLabel
+          checked={selectedIds.has(row.id)}
+          onChange={(checked) => toggleOne(row.id, checked)}
+        />
+      ),
     },
     {
       key: 'no',
@@ -329,7 +344,10 @@
               }
             />
           </div>
-          <FoxButton type="secondary" size="md" label="적용" onAction={applyFilters} />
+          <FoxButtonGroup size="md">
+            <FoxButton type="secondary" size="md" label="적용" onAction={applyFilters} />
+            <FoxButton type="default" size="md" label="초기화" onAction={resetFilters} />
+          </FoxButtonGroup>
         </div>
       }
       searchFields={[]}
Add a comment
List