"use client"; import { ReactNode, Fragment, useState, RefObject } from "react"; import { LucideIcon, Trash2 } from "lucide-react"; import { Card, CardContent } from "@/components/ui/card"; import { Tabs, TabsContent } from "@/components/ui/tabs"; import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Checkbox } from "@/components/ui/checkbox"; import { Button } from "@/components/ui/button"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { PageLayout } from "@/components/organisms/PageLayout"; import { PageHeader } from "@/components/organisms/PageHeader"; import { StatCards } from "@/components/organisms/StatCards"; import { SearchFilter } from "@/components/organisms/SearchFilter"; import { ScreenVersionHistory } from "@/components/organisms/ScreenVersionHistory"; import { TabChip } from "@/components/atoms/TabChip"; /** * 기본 통합 목록_버젼2 * * 품목관리 스타일의 완전한 목록 템플릿 * - PageHeader, StatCards, SearchFilter, ScreenVersionHistory * - 탭 기반 필터 (데스크톱: TabsList, 모바일: 커스텀 버튼) * - 체크박스 포함 DataTable (Desktop) * - 체크박스 포함 모바일 카드 (Mobile) * - 페이지네이션 */ export interface TabOption { value: string; label: string; count: number; color?: string; // 모바일 탭 색상 } export interface TableColumn { key: string; label: string; className?: string; hideOnMobile?: boolean; hideOnTablet?: boolean; } export interface PaginationConfig { currentPage: number; totalPages: number; totalItems: number; itemsPerPage: number; onPageChange: (page: number) => void; } export interface StatCard { label: string; value: string | number; icon: LucideIcon; iconColor: string; } export interface VersionHistoryItem { version: string; description: string; modifiedBy: string; modifiedAt: string; } export interface DevMetadata { componentName: string; pagePath: string; description: string; apis?: any[]; dataStructures?: any[]; dbSchema?: any[]; businessLogic?: any[]; } export interface IntegratedListTemplateV2Props { // 페이지 헤더 title: string; description?: string; icon?: LucideIcon; headerActions?: ReactNode; // 탭 콘텐츠 (헤더 액션 아래, 검색 위에 표시되는 커스텀 탭) tabsContent?: ReactNode; // 통계 카드 stats?: StatCard[]; // 경고 배너 (통계 카드와 검색 영역 사이) alertBanner?: ReactNode; // 버전 이력 versionHistory?: VersionHistoryItem[]; versionHistoryTitle?: string; // 검색 및 필터 searchValue?: string; onSearchChange?: (value: string) => void; searchPlaceholder?: string; extraFilters?: ReactNode; // Select, DatePicker 등 추가 필터 hideSearch?: boolean; // 검색창 숨김 여부 // 탭 (품목 유형, 상태 등) - optional tabs?: TabOption[]; activeTab?: string; onTabChange?: (value: string) => void; // 테이블 헤더 액션 (탭 옆에 표시될 셀렉트박스 등) tableHeaderActions?: ReactNode; // 테이블 앞에 표시될 컨텐츠 (계정과목명 + 저장 버튼 등) beforeTableContent?: ReactNode; // 테이블 컬럼 tableColumns: TableColumn[]; tableTitle?: string; // "전체 목록 (100개)" 같은 타이틀 // 테이블 하단 푸터 (합계 등) tableFooter?: ReactNode; // 데이터 data: T[]; // 데스크톱용 페이지네이션된 데이터 totalCount?: number; // 전체 데이터 개수 (역순 번호 계산용) allData?: T[]; // 모바일 인피니티 스크롤용 전체 필터된 데이터 mobileDisplayCount?: number; // 모바일에서 표시할 개수 onLoadMore?: () => void; // 더 불러오기 콜백 infinityScrollSentinelRef?: RefObject; // 인피니티 스크롤용 sentinel ref // 체크박스 선택 selectedItems: Set; onToggleSelection: (id: string) => void; onToggleSelectAll: () => void; getItemId: (item: T) => string; // 아이템에서 ID 추출 onBulkDelete?: () => void; // 일괄 삭제 핸들러 // 테이블 표시 옵션 showCheckbox?: boolean; // 체크박스 표시 여부 (기본: true) showRowNumber?: boolean; // 번호 컬럼 표시 여부 (기본: true, tableColumns에 번호 포함 시) // 렌더링 함수 renderTableRow: (item: T, index: number, globalIndex: number) => ReactNode; renderMobileCard: (item: T, index: number, globalIndex: number, isSelected: boolean, onToggle: () => void) => ReactNode; // 페이지네이션 pagination: PaginationConfig; // 개발자 메타데이터 devMetadata?: DevMetadata; } export function IntegratedListTemplateV2({ title, description, icon, headerActions, tabsContent, stats, alertBanner, versionHistory, versionHistoryTitle = "수정 이력", searchValue, onSearchChange, searchPlaceholder = "검색...", extraFilters, hideSearch = false, tabs, activeTab, onTabChange, tableHeaderActions, beforeTableContent, tableColumns, tableTitle, tableFooter, data, totalCount, allData, mobileDisplayCount, onLoadMore, infinityScrollSentinelRef, selectedItems, onToggleSelection, onToggleSelectAll, getItemId, onBulkDelete, showCheckbox = true, // 기본값 true showRowNumber = true, // 기본값 true (번호 컬럼은 renderTableRow에서 처리) renderTableRow, renderMobileCard, pagination, devMetadata, }: IntegratedListTemplateV2Props) { const [showDeleteDialog, setShowDeleteDialog] = useState(false); const startIndex = (pagination.currentPage - 1) * pagination.itemsPerPage; const allSelected = selectedItems.size === data.length && data.length > 0; // 일괄삭제 확인 핸들러 const handleBulkDeleteClick = () => { setShowDeleteDialog(true); }; // 일괄삭제 실행 const handleConfirmDelete = () => { if (onBulkDelete) { onBulkDelete(); } setShowDeleteDialog(false); }; return ( {/* 페이지 헤더 */} {/* 헤더 액션 (달력, 버튼 등) - 타이틀 아래 배치 */} {headerActions && (
{headerActions}
)} {/* 커스텀 탭 콘텐츠 (헤더 아래, 검색 위) */} {tabsContent && (
{tabsContent}
)} {/* 통계 카드 - 태블릿/데스크톱 */} {stats && stats.length > 0 && (
)} {/* 경고 배너 (통계 카드와 검색 영역 사이) */} {alertBanner} {/* 버전 이력 */} {versionHistory && versionHistory.length > 0 && ( )} {/* 검색 및 필터 */} {!hideSearch && ( {})} searchPlaceholder={searchPlaceholder} filterButton={false} extraActions={extraFilters} /> )} {/* 테이블 앞 컨텐츠 (계정과목명 + 저장 버튼 등) */} {beforeTableContent && (
{beforeTableContent}
)} {/* 목록 카드 */} {/* 데스크톱 (1280px+) - TabChip 탭 */}
{tabs && tabs.map((tab) => ( onTabChange?.(tab.value)} color={tab.color as any} /> ))}
{/* 선택된 항목 수 표시 */} {selectedItems.size > 0 && ( {selectedItems.size}개 항목 선택됨 )} {/* 테이블 헤더 액션 (필터/정렬 셀렉트박스 등) */} {tableHeaderActions} {selectedItems.size >= 1 && onBulkDelete && ( )}
{/* 탭 컨텐츠 */} {(tabs || [{ value: 'default', label: '', count: 0 }]).map((tab) => ( {/* 모바일/태블릿/소형 노트북 (~1279px) - 선택 삭제 버튼 */} {selectedItems.size >= 2 && onBulkDelete && (
)} {/* 모바일/태블릿/소형 노트북 (~1279px) 카드 뷰 */}
{(allData && allData.length > 0 ? allData : data).length === 0 ? (
검색 결과가 없습니다.
) : ( // 백엔드가 created_at ASC로 정렬해서 보내줌 (오래된 순) (allData || data).map((item, index) => { const itemId = getItemId(item); const isSelected = selectedItems.has(itemId); // 순차 번호: 1번부터 시작 const globalIndex = index + 1; return (
{renderMobileCard( item, index, globalIndex, isSelected, () => onToggleSelection(itemId) )}
); }) )} {/* 인피니티 스크롤 Sentinel */} {infinityScrollSentinelRef && ( {/* 데스크톱 (1280px+) 테이블 뷰 */}
{showCheckbox && ( )} {tableColumns.map((column) => { // "actions" 컬럼은 항상 렌더링하되, 선택된 항목이 없을 때는 빈 헤더로 표시 return ( {column.key === "actions" && selectedItems.size === 0 ? "" : column.label} ); })} {data.length === 0 ? ( 검색 결과가 없습니다. ) : ( // 백엔드가 created_at ASC로 정렬해서 보내줌 (오래된 순) data.map((item, index) => { const itemId = getItemId(item); // 순차 번호: startIndex 기준으로 1부터 시작 const globalIndex = startIndex + index + 1; return ( {renderTableRow(item, index, globalIndex)} ); }) )} {tableFooter && ( {tableFooter} )}
))} {/* 페이지네이션 - 데스크톱에서만 표시 */} {pagination.totalPages > 1 && (
전체 {pagination.totalItems}개 중 {startIndex + 1}-{Math.min(startIndex + pagination.itemsPerPage, pagination.totalItems)}개 표시
{Array.from({ length: pagination.totalPages }, (_, i) => i + 1).map((page) => { // 현재 페이지 근처만 표시 if ( page === 1 || page === pagination.totalPages || (page >= pagination.currentPage - 2 && page <= pagination.currentPage + 2) ) { return ( ); } else if ( page === pagination.currentPage - 3 || page === pagination.currentPage + 3 ) { return ...; } return null; })}
)} {/* 일괄 삭제 확인 다이얼로그 - 단일 삭제와 동일한 디자인 */} ⚠️ 삭제 확인

선택한 {selectedItems.size}개의 항목을 삭제하시겠습니까?

⚠️
주의
삭제된 항목은 복구할 수 없습니다. 관련된 데이터도 함께 삭제될 수 있습니다.
취소 삭제
); }