diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-17 09:08:16 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-17 09:08:16 +0000 |
| commit | f7117370b9cc0c7b96bd1eb23a1b9f5b16cc8ceb (patch) | |
| tree | 4efb0a5ce420b44a402810fc19c35afc92ec5271 /components/common/material/material-group-selector.tsx | |
| parent | c54e2acaed641b7ae2c1a7304b08626f9ca973db (diff) | |
| parent | 7433eea5b4bbc0899e255b88e1a7e91f26e9d95b (diff) | |
Merge branch 'dujinkim' of https://github.com/DTS-Development/SHI_EVCP into dujinkim
Diffstat (limited to 'components/common/material/material-group-selector.tsx')
| -rw-r--r-- | components/common/material/material-group-selector.tsx | 80 |
1 files changed, 54 insertions, 26 deletions
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> |
