From 7433eea5b4bbc0899e255b88e1a7e91f26e9d95b Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Wed, 17 Sep 2025 18:04:10 +0900
Subject: (김준회) 자재그룹 선택기 오류수정, 공용사용을 위한 다이얼로그
 컴포넌트 구현, data-table 오류 수정
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../common/material/material-group-selector.tsx    | 80 +++++++++++++++-------
 1 file changed, 54 insertions(+), 26 deletions(-)

(limited to 'components/common/material/material-group-selector.tsx')

diff --git a/components/common/material/material-group-selector.tsx b/components/common/material/material-group-selector.tsx
index 69a929f2..f0276810 100644
--- a/components/common/material/material-group-selector.tsx
+++ b/components/common/material/material-group-selector.tsx
@@ -28,11 +28,11 @@ interface MaterialGroupSelectorProps {
   placeholder?: string;
   noValuePlaceHolder?: string;
   disabled?: boolean;
-  maxSelections?: number;
   className?: string;
   closeOnSelect?: boolean;
   excludeMaterialCodes?: Set<string>; // 제외할 자재그룹코드들
   showInitialData?: boolean; // 초기 클릭시 자재그룹들을 로드할지 여부
+  maxSelections?: number; // 최대 선택 가능한 자재 개수 (1이면 단일 선택, undefined면 제한 없음)
 }
 
 export function MaterialGroupSelector({
@@ -42,11 +42,11 @@ export function MaterialGroupSelector({
   placeholder = "자재를 검색하세요...",
   noValuePlaceHolder = "자재를 검색해주세요",
   disabled = false,
-  maxSelections,
   className,
   closeOnSelect = true,
   excludeMaterialCodes,
-  showInitialData = true
+  showInitialData = true,
+  maxSelections
 }: MaterialGroupSelectorProps) {
   
   const [open, setOpen] = useState(false);
@@ -153,22 +153,27 @@ export function MaterialGroupSelector({
 
     let newSelectedMaterials: MaterialSearchItem[];
 
-    if (singleSelect) {
+    // maxSelections가 1이면 단일 선택 모드로 동작
+    const isSingleSelectMode = singleSelect || maxSelections === 1;
+
+    if (isSingleSelectMode) {
       newSelectedMaterials = [material];
     } else {
       const isAlreadySelected = selectedMaterials.some(
         (selected) => selected.materialGroupCode === material.materialGroupCode && 
-                      selected.materialGroupDesc === material.materialGroupDesc
+                      selected.materialGroupDescription === material.materialGroupDescription
       );
 
       if (isAlreadySelected) {
         newSelectedMaterials = selectedMaterials.filter(
           (selected) => !(selected.materialGroupCode === material.materialGroupCode && 
-                         selected.materialGroupDesc === material.materialGroupDesc)
+                         selected.materialGroupDescription === material.materialGroupDescription)
         );
       } else {
+        // 최대 선택 개수 확인
         if (maxSelections && selectedMaterials.length >= maxSelections) {
-          return; // 최대 선택 수 초과 시 추가하지 않음
+          // 최대 개수에 도달한 경우 선택하지 않음
+          return;
         }
         newSelectedMaterials = [...selectedMaterials, material];
       }
@@ -176,10 +181,10 @@ export function MaterialGroupSelector({
 
     onMaterialsChange?.(newSelectedMaterials);
 
-    if (closeOnSelect && singleSelect) {
+    if (closeOnSelect && isSingleSelectMode) {
       setOpen(false);
     }
-  }, [disabled, singleSelect, selectedMaterials, maxSelections, onMaterialsChange, closeOnSelect]);
+  }, [disabled, singleSelect, maxSelections, selectedMaterials, onMaterialsChange, closeOnSelect]);
 
   // 개별 자재 제거
   const handleRemoveMaterial = useCallback((materialToRemove: MaterialSearchItem) => {
@@ -187,7 +192,7 @@ export function MaterialGroupSelector({
     
     const newSelectedMaterials = selectedMaterials.filter(
       (material) => !(material.materialGroupCode === materialToRemove.materialGroupCode && 
-                     material.materialGroupDesc === materialToRemove.materialGroupDesc)
+                     material.materialGroupDescription === materialToRemove.materialGroupDescription)
     );
     onMaterialsChange?.(newSelectedMaterials);
   }, [disabled, selectedMaterials, onMaterialsChange]);
@@ -196,7 +201,7 @@ export function MaterialGroupSelector({
   const isMaterialSelected = useCallback((material: MaterialSearchItem) => {
     return selectedMaterials.some(
       (selected) => selected.materialGroupCode === material.materialGroupCode && 
-                    selected.materialGroupDesc === material.materialGroupDesc
+                    selected.materialGroupDescription === material.materialGroupDescription
     );
   }, [selectedMaterials]);
 
@@ -217,11 +222,11 @@ export function MaterialGroupSelector({
               ) : (
                 selectedMaterials.map((material) => (
                   <Badge
-                    key={`${material.materialGroupCode}-${material.materialGroupDesc}`}
+                    key={`${material.materialGroupCode}-${material.materialGroupDescription}`}
                     variant="secondary"
                     className="gap-1 pr-1"
                   >
-                    <span className="max-w-[200px] truncate">
+                    <span className="">
                       {material.displayText}
                     </span>
                     {!disabled && (
@@ -257,8 +262,17 @@ export function MaterialGroupSelector({
               />
             </div>
 
-            <CommandList>
-              <div className="h-64 overflow-y-auto">
+            {/* 스크롤 컨테이너 + 고정 페이지네이션 */}
+            <div className="max-h-[50vh] flex flex-col">
+              <CommandList 
+                className="flex-1 overflow-y-auto overflow-x-hidden min-h-0"
+                // shadcn CommandList 버그 처리 - 스크롤 이벤트 전파 차단
+                onWheel={(e) => {
+                  e.stopPropagation(); // 이벤트 전파 차단
+                  const target = e.currentTarget;
+                  target.scrollTop += e.deltaY; // 직접 스크롤 처리
+                }}
+              >
                 {!searchQuery.trim() && !showInitialData ? (
                   <div className="p-4 text-center text-sm text-muted-foreground">
                     자재를 검색하려면 검색어를 입력해주세요.
@@ -278,27 +292,31 @@ export function MaterialGroupSelector({
                 ) : searchResults.length === 0 ? (
                   <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
                 ) : (
-                  <CommandGroup>
+                  <CommandGroup className="overflow-visible">
                     {searchResults.map((material) => {
                       const isExcluded = excludeMaterialCodes?.has(material.materialGroupCode);
                       const isSelected = isMaterialSelected(material);
-                      
+                      const isMaxReached = maxSelections && selectedMaterials.length >= maxSelections && !isSelected;
+                      const isDisabled = isExcluded || isMaxReached;
+
                       return (
                         <CommandItem
-                          key={`${material.materialGroupCode}-${material.materialGroupDesc}`}
+                          key={`${material.materialGroupCode}-${material.materialGroupDescription}`}
                           onSelect={() => {
-                            if (!isExcluded) {
+                            if (!isDisabled) {
                               handleMaterialSelect(material);
                             }
                           }}
                           className={cn(
                             "cursor-pointer",
-                            isExcluded && "opacity-50 cursor-not-allowed bg-muted"
+                            isDisabled && "opacity-50 cursor-not-allowed bg-muted"
                           )}
                         >
                           <div className="mr-2 h-4 w-4 flex items-center justify-center">
                             {isExcluded ? (
                               <span className="text-xs text-muted-foreground">✓</span>
+                            ) : isMaxReached ? (
+                              <span className="text-xs text-muted-foreground">-</span>
                             ) : (
                               <Check
                                 className={cn(
@@ -311,17 +329,27 @@ export function MaterialGroupSelector({
                           <div className="flex-1">
                             <div className={cn(
                               "font-medium",
-                              isExcluded && "text-muted-foreground"
+                              isDisabled && "text-muted-foreground"
                             )}>
-                              {material.materialGroupDesc}
+                              {material.materialGroupDescription}
                               {isExcluded && (
                                 <span className="ml-2 text-xs bg-red-100 text-red-600 px-2 py-1 rounded">
                                   이미 등록됨
                                 </span>
                               )}
+                              {isMaxReached && (
+                                <span className="ml-2 text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded">
+                                  선택 제한 ({maxSelections}개)
+                                </span>
+                              )}
                             </div>
                             <div className="text-xs text-muted-foreground">
                               자재그룹코드: {material.materialGroupCode}
+                              {material.materialGroupUom && (
+                                <span className="ml-2">
+                                  | UOM: {material.materialGroupUom}
+                                </span>
+                              )}
                             </div>
                           </div>
                         </CommandItem>
@@ -329,11 +357,11 @@ export function MaterialGroupSelector({
                     })}
                   </CommandGroup>
                 )}
-              </div>
+              </CommandList>
 
-              {/* 페이지네이션 */}
+              {/* 고정 페이지네이션 - 항상 밑에 표시 */}
               {searchResults.length > 0 && pagination.pageCount > 1 && (
-                <div className="flex items-center justify-between border-t px-3 py-2">
+                <div className="flex items-center justify-between border-t px-3 py-2 flex-shrink-0">
                   <div className="text-xs text-muted-foreground">
                     총 {pagination.total}개 중 {((pagination.page - 1) * pagination.perPage) + 1}-
                     {Math.min(pagination.page * pagination.perPage, pagination.total)}개 표시
@@ -363,7 +391,7 @@ export function MaterialGroupSelector({
                   </div>
                 </div>
               )}
-            </CommandList>
+            </div>
           </Command>
         </PopoverContent>
       </Popover>
-- 
cgit v1.2.3

