"use client"; import React, { useState, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { MaterialGroupSelector } from "./material-group-selector"; import { MaterialSearchItem } from "@/lib/material/material-group-service"; import { X } from "lucide-react"; /** * 자재그룹 다중 선택 Dialog 컴포넌트 * * @description * - MaterialGroupSelector를 Dialog로 래핑한 다중 선택 컴포넌트 * - 버튼 클릭 시 Dialog가 열리고, 여러 자재를 선택한 후 확인 버튼으로 완료 * - 최대 선택 개수 제한 가능 * * @MaterialSearchItem_Structure * 상태에서 관리되는 자재그룹 객체의 형태: * ```typescript * interface MaterialSearchItem { * materialGroupCode: string; // 자재그룹코드 (예: "BG2001") * materialGroupDescription: string; // 자재그룹명 (예: "DOUBLE WETDOOR HINGE") * materialGroupUom?: string; // 단위 (예: "EA", "KG") - 선택적 * displayText: string; // 표시용 텍스트 (code + " - " + description) * } * ``` * * @state * - open: Dialog 열림/닫힘 상태 * - selectedMaterials: 현재 선택된 자재들 (배열) * - tempSelectedMaterials: Dialog 내에서 임시로 선택된 자재들 (확인 버튼 클릭 전까지) * * @callback * - onMaterialsSelect: 자재 선택 완료 시 호출되는 콜백 * - 매개변수: MaterialSearchItem[] * - 선택된 자재들의 배열 (빈 배열일 수도 있음) * * @usage * ```tsx * { * setSelectedMaterials(materials); * console.log('선택된 자재들:', materials); * }} * maxSelections={5} * placeholder="자재를 검색하세요..." * /> * ``` */ interface MaterialGroupSelectorDialogMultiProps { /** Dialog를 여는 트리거 버튼 텍스트 */ triggerLabel?: string; /** 현재 선택된 자재들 */ selectedMaterials?: MaterialSearchItem[]; /** 자재 선택 완료 시 호출되는 콜백 */ onMaterialsSelect?: (materials: MaterialSearchItem[]) => void; /** 최대 선택 가능한 자재 개수 */ maxSelections?: number; /** 검색 입력창 placeholder */ placeholder?: string; /** Dialog 제목 */ title?: string; /** Dialog 설명 */ description?: string; /** 트리거 버튼 비활성화 여부 */ disabled?: boolean; /** 트리거 버튼 variant */ triggerVariant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"; /** 제외할 자재그룹코드들 */ excludeMaterialCodes?: Set; /** 초기 데이터 표시 여부 */ showInitialData?: boolean; /** 트리거 버튼에서 선택된 자재들을 표시할지 여부 */ showSelectedInTrigger?: boolean; } export function MaterialGroupSelectorDialogMulti({ triggerLabel = "자재 선택", selectedMaterials = [], onMaterialsSelect, maxSelections, placeholder = "자재를 검색하세요...", title = "자재 선택 (다중)", description, disabled = false, triggerVariant = "outline", excludeMaterialCodes, showInitialData = true, showSelectedInTrigger = true, }: MaterialGroupSelectorDialogMultiProps) { // Dialog 열림/닫힘 상태 const [open, setOpen] = useState(false); // Dialog 내에서 임시로 선택된 자재들 (확인 버튼 클릭 전까지) const [tempSelectedMaterials, setTempSelectedMaterials] = useState([]); // Dialog 설명 동적 생성 const dialogDescription = description || (maxSelections ? `원하는 자재를 검색하고 선택해주세요. (최대 ${maxSelections}개)` : "원하는 자재를 검색하고 선택해주세요." ); // Dialog 열림 시 현재 선택된 자재들로 임시 선택 초기화 const handleOpenChange = useCallback((newOpen: boolean) => { setOpen(newOpen); if (newOpen) { setTempSelectedMaterials([...selectedMaterials]); } }, [selectedMaterials]); // 자재 선택 처리 (Dialog 내에서) const handleMaterialsChange = useCallback((materials: MaterialSearchItem[]) => { setTempSelectedMaterials(materials); }, []); // 확인 버튼 클릭 시 선택 완료 const handleConfirm = useCallback(() => { onMaterialsSelect?.(tempSelectedMaterials); setOpen(false); }, [tempSelectedMaterials, onMaterialsSelect]); // 취소 버튼 클릭 시 const handleCancel = useCallback(() => { setTempSelectedMaterials([...selectedMaterials]); setOpen(false); }, [selectedMaterials]); // 전체 선택 해제 const handleClearAll = useCallback(() => { setTempSelectedMaterials([]); }, []); // 개별 자재 제거 (트리거 버튼에서) const handleRemoveMaterial = useCallback((materialToRemove: MaterialSearchItem, e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); const newMaterials = selectedMaterials.filter( (material) => !(material.materialGroupCode === materialToRemove.materialGroupCode && material.materialGroupDescription === materialToRemove.materialGroupDescription) ); onMaterialsSelect?.(newMaterials); }, [selectedMaterials, onMaterialsSelect]); // 트리거 버튼 렌더링 const renderTriggerContent = () => { if (selectedMaterials.length === 0) { return triggerLabel; } if (!showSelectedInTrigger) { return `${triggerLabel} (${selectedMaterials.length}개)`; } return (
{triggerLabel}: {selectedMaterials.slice(0, 2).map((material) => ( {material.materialGroupDescription} {!disabled && ( )} ))} {selectedMaterials.length > 2 && ( +{selectedMaterials.length - 2}개 )}
); }; return ( {title} {dialogDescription}
{/* 선택된 자재 개수 표시 */}
{maxSelections ? ( `선택됨: ${tempSelectedMaterials.length}/${maxSelections}개` ) : ( `선택됨: ${tempSelectedMaterials.length}개` )}
{tempSelectedMaterials.length > 0 && ( )}
); } /** * 사용 예시: * * ```tsx * const [selectedMaterials, setSelectedMaterials] = useState([]); * * return ( * { * setSelectedMaterials(materials); * console.log('선택된 자재들:', materials.map(m => ({ * code: m.materialGroupCode, * description: m.materialGroupDescription, * uom: m.materialGroupUom * }))); * }} * maxSelections={5} * title="필수 자재 선택" * description="프로젝트에 필요한 자재들을 선택해주세요." * showSelectedInTrigger={true} * /> * ); * ``` * * @advanced_usage * ```tsx * // 제외할 자재가 있는 경우 * const excludedCodes = new Set(['MAT001', 'MAT002']); * * * ``` */