diff options
| author | joonhoekim <26rote@gmail.com> | 2025-09-04 02:24:27 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-09-04 02:24:27 +0000 |
| commit | 39f21a1e0f7491e9e70a2e41f07d691d179b3126 (patch) | |
| tree | 94aae159455840659c9e8e31f85cb96092fcda0f /components/common | |
| parent | e23f3383eb75e8033ab918e31a1e5f53446e13ca (diff) | |
(김준회) material-selector(자재그룹선택기) 공용컴포넌트 구현
Diffstat (limited to 'components/common')
| -rw-r--r-- | components/common/material/README.md | 82 | ||||
| -rw-r--r-- | components/common/material/material-selector.tsx | 36 |
2 files changed, 109 insertions, 9 deletions
diff --git a/components/common/material/README.md b/components/common/material/README.md new file mode 100644 index 00000000..30f16d38 --- /dev/null +++ b/components/common/material/README.md @@ -0,0 +1,82 @@ +# MaterialSelector + +자재그룹코드를 검색하고 선택할 수 있는 드롭다운 컴포넌트 + +## 주요 기능 + +- 자재그룹코드 및 자재그룹코드명(=자재명) 으로 검색 및 선택 +- 단일/다중 선택 모드 +- 페이지네이션 10페이지로 구현 +- 초기 데이터 로드 여부 설정 가능 (기본값은 초기 데이터 로드 활성화, 벤더 회원가입시 공급품목에서는 비활성화) + +## 사용 예시 + +`selectedMaterials` 상태에 선택된 자재 정보가 저장되며, 이를 통해 선택된 자재를 활용할 수 있습니다. + +### 기본 사용법 +```tsx +import { MaterialSelector } from "@/components/common/material/material-selector"; + +function MyComponent() { + const [selectedMaterials, setSelectedMaterials] = useState<MaterialSearchItem[]>([]); + + return ( + <MaterialSelector + selectedMaterials={selectedMaterials} + onMaterialsChange={setSelectedMaterials} + /> + ); +} +``` + +### 단일 선택 모드 +```tsx +<MaterialSelector + selectedMaterials={selectedMaterials} + onMaterialsChange={setSelectedMaterials} + singleSelect={true} + placeholder="자재를 선택하세요..." +/> +``` + +### 초기 데이터 로드 비활성화 +```tsx +<MaterialSelector + selectedMaterials={selectedMaterials} + onMaterialsChange={setSelectedMaterials} + showInitialData={false} +/> +``` + +### 최대 선택 개수 제한 +```tsx +<MaterialSelector + selectedMaterials={selectedMaterials} + onMaterialsChange={setSelectedMaterials} + maxSelections={5} +/> +``` + +### 특정 자재코드 제외 +```tsx +const excludeCodes = new Set(['MAT001', 'MAT002']); + +<MaterialSelector + selectedMaterials={selectedMaterials} + onMaterialsChange={setSelectedMaterials} + excludeMaterialCodes={excludeCodes} +/> +``` + +## Props + +- `selectedMaterials` (`MaterialSearchItem[]`, 기본값: `[]`) - 선택된 자재 목록 +- `onMaterialsChange` (`(materials: MaterialSearchItem[]) => void`) - 자재 선택 변경 콜백 +- `singleSelect` (`boolean`, 기본값: `false`) - 단일 선택 모드 여부 +- `showInitialData` (`boolean`, 기본값: `true`) - 초기 클릭시 자재 목록 로드 여부 +- `maxSelections` (`number`) - 최대 선택 가능 개수 +- `excludeMaterialCodes` (`Set<string>`) - 제외할 자재그룹코드 목록 +- `disabled` (`boolean`, 기본값: `false`) - 비활성화 여부 +- `placeholder` (`string`, 기본값: `"자재를 검색하세요..."`) - 검색 입력 플레이스홀더 +- `noValuePlaceHolder` (`string`, 기본값: `"자재를 검색해주세요"`) - 선택된 값이 없을 때 표시 텍스트 +- `closeOnSelect` (`boolean`, 기본값: `true`) - 선택 후 드롭다운 닫기 여부 diff --git a/components/common/material/material-selector.tsx b/components/common/material/material-selector.tsx index b24a2f4f..67b8c25c 100644 --- a/components/common/material/material-selector.tsx +++ b/components/common/material/material-selector.tsx @@ -4,7 +4,6 @@ import React, { useState, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; -import { ScrollArea } from "@/components/ui/scroll-area"; import { Popover, PopoverContent, @@ -33,6 +32,7 @@ interface MaterialSelectorProps { className?: string; closeOnSelect?: boolean; excludeMaterialCodes?: Set<string>; // 제외할 자재그룹코드들 + showInitialData?: boolean; // 초기 클릭시 자재그룹들을 로드할지 여부 } export function MaterialSelector({ @@ -45,7 +45,8 @@ export function MaterialSelector({ maxSelections, className, closeOnSelect = true, - excludeMaterialCodes + excludeMaterialCodes, + showInitialData = true }: MaterialSelectorProps) { const [open, setOpen] = useState(false); @@ -54,6 +55,7 @@ export function MaterialSelector({ const [isSearching, setIsSearching] = useState(false); const [searchError, setSearchError] = useState<string | null>(null); const [currentPage, setCurrentPage] = useState(1); + const [initialDataLoaded, setInitialDataLoaded] = useState(false); const [pagination, setPagination] = useState({ page: 1, perPage: 10, @@ -107,11 +109,22 @@ export function MaterialSelector({ } }, []); + // Popover 열림시 초기 데이터 로드 + React.useEffect(() => { + if (open && showInitialData && !initialDataLoaded && !searchQuery.trim()) { + setInitialDataLoaded(true); + performSearch("", 1); // 빈 쿼리로 초기 데이터 로드 + } + }, [open, showInitialData, initialDataLoaded, searchQuery, performSearch]); + // Debounced 검색어 변경 시 검색 실행 (검색어가 있을 때만) React.useEffect(() => { if (debouncedSearchQuery.trim()) { setCurrentPage(1); performSearch(debouncedSearchQuery, 1); + } else if (showInitialData && initialDataLoaded) { + // 검색어가 없고 초기 데이터를 보여주는 경우 초기 데이터 유지 + // 아무것도 하지 않음 (기존 데이터 유지) } else { // 검색어가 없으면 결과 초기화 setSearchResults([]); @@ -124,14 +137,15 @@ export function MaterialSelector({ hasPrevPage: false, }); } - }, [debouncedSearchQuery, performSearch]); + }, [debouncedSearchQuery, performSearch, showInitialData, initialDataLoaded]); // 페이지 변경 처리 - useCallback으로 메모이제이션 const handlePageChange = useCallback((newPage: number) => { if (newPage >= 1 && newPage <= pagination.pageCount) { - performSearch(debouncedSearchQuery, newPage); + const query = debouncedSearchQuery.trim() || (showInitialData && initialDataLoaded ? "" : debouncedSearchQuery); + performSearch(query, newPage); } - }, [pagination.pageCount, performSearch, debouncedSearchQuery]); + }, [pagination.pageCount, performSearch, debouncedSearchQuery, showInitialData, initialDataLoaded]); // 자재 선택 처리 - useCallback으로 메모이제이션 const handleMaterialSelect = useCallback((material: MaterialSearchItem) => { @@ -244,11 +258,15 @@ export function MaterialSelector({ </div> <CommandList> - <ScrollArea className="h-64"> - {!searchQuery.trim() ? ( + <div className="h-64 overflow-y-auto"> + {!searchQuery.trim() && !showInitialData ? ( <div className="p-4 text-center text-sm text-muted-foreground"> 자재를 검색하려면 검색어를 입력해주세요. </div> + ) : !searchQuery.trim() && showInitialData && !initialDataLoaded ? ( + <div className="p-4 text-center text-sm text-muted-foreground"> + 자재 목록을 로드하려면 클릭해주세요. + </div> ) : isSearching ? ( <div className="p-4 text-center text-sm text-muted-foreground"> 검색 중... @@ -303,7 +321,7 @@ export function MaterialSelector({ )} </div> <div className="text-xs text-muted-foreground"> - 코드: {material.materialGroupCode} + 자재그룹코드: {material.materialGroupCode} </div> </div> </CommandItem> @@ -311,7 +329,7 @@ export function MaterialSelector({ })} </CommandGroup> )} - </ScrollArea> + </div> {/* 페이지네이션 */} {searchResults.length > 0 && pagination.pageCount > 1 && ( |
