fix(WEB): 공정관리 개별 품목 저장 안되는 버그 수정
- selectedItemCodes → selectedItemIds로 변경 - item.code 대신 item.id 사용하여 API에 올바른 ID 전달 - 검색어 유효성 검사 추가 (한글 1자, 영문 2자 이상) - 품목 조회 size 100 → 1000으로 변경
This commit is contained in:
@@ -74,7 +74,7 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
// 개별 품목용 상태
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [selectedItemType, setSelectedItemType] = useState('all');
|
||||
const [selectedItemCodes, setSelectedItemCodes] = useState<Set<string>>(new Set());
|
||||
const [selectedItemIds, setSelectedItemIds] = useState<Set<string>>(new Set());
|
||||
|
||||
// 품목 목록 API 상태
|
||||
const [itemList, setItemList] = useState<ItemOption[]>([]);
|
||||
@@ -86,16 +86,35 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
const items = await getItemList({
|
||||
q: q || undefined,
|
||||
itemType: itemType === 'all' ? undefined : itemType,
|
||||
size: 100,
|
||||
size: 1000, // 전체 품목 조회
|
||||
});
|
||||
setItemList(items);
|
||||
setIsItemsLoading(false);
|
||||
}, []);
|
||||
|
||||
// 검색어 유효성 검사 함수
|
||||
const isValidSearchKeyword = (keyword: string): boolean => {
|
||||
if (!keyword || keyword.trim() === '') return false;
|
||||
|
||||
const trimmed = keyword.trim();
|
||||
// 한글이 포함되어 있으면 1자 이상
|
||||
const hasKorean = /[가-힣]/.test(trimmed);
|
||||
if (hasKorean) return trimmed.length >= 1;
|
||||
|
||||
// 영어/숫자만 있으면 2자 이상
|
||||
return trimmed.length >= 2;
|
||||
};
|
||||
|
||||
// 검색어/품목유형 변경 시 API 호출 (debounce)
|
||||
useEffect(() => {
|
||||
if (registrationType !== 'individual') return;
|
||||
|
||||
// 검색어 유효성 검사 - 유효하지 않으면 빈 목록
|
||||
if (!isValidSearchKeyword(searchKeyword)) {
|
||||
setItemList([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
loadItems(searchKeyword, selectedItemType);
|
||||
}, 300);
|
||||
@@ -103,21 +122,30 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchKeyword, selectedItemType, registrationType, loadItems]);
|
||||
|
||||
// 모달 열릴 때 품목 목록 초기 로드
|
||||
// 품목유형 변경 시 검색어가 유효하면 재검색
|
||||
useEffect(() => {
|
||||
if (registrationType !== 'individual') return;
|
||||
if (!isValidSearchKeyword(searchKeyword)) return;
|
||||
|
||||
loadItems(searchKeyword, selectedItemType);
|
||||
}, [selectedItemType]);
|
||||
|
||||
// 모달 열릴 때 품목 목록 초기화 (초기 로드 안함)
|
||||
useEffect(() => {
|
||||
if (open && registrationType === 'individual') {
|
||||
loadItems('', 'all');
|
||||
setItemList([]);
|
||||
setSearchKeyword('');
|
||||
}
|
||||
}, [open, registrationType, loadItems]);
|
||||
}, [open, registrationType]);
|
||||
|
||||
// 체크박스 토글
|
||||
const handleToggleItem = (code: string) => {
|
||||
setSelectedItemCodes((prev) => {
|
||||
const handleToggleItem = (id: string) => {
|
||||
setSelectedItemIds((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(code)) {
|
||||
newSet.delete(code);
|
||||
if (newSet.has(id)) {
|
||||
newSet.delete(id);
|
||||
} else {
|
||||
newSet.add(code);
|
||||
newSet.add(id);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
@@ -125,13 +153,13 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
|
||||
// 전체 선택
|
||||
const handleSelectAll = () => {
|
||||
const allCodes = itemList.map((item) => item.code);
|
||||
setSelectedItemCodes(new Set(allCodes));
|
||||
const allIds = itemList.map((item) => item.id);
|
||||
setSelectedItemIds(new Set(allIds));
|
||||
};
|
||||
|
||||
// 초기화
|
||||
const handleResetSelection = () => {
|
||||
setSelectedItemCodes(new Set());
|
||||
setSelectedItemIds(new Set());
|
||||
};
|
||||
|
||||
// 모달 열릴 때 초기화 또는 수정 데이터 로드
|
||||
@@ -149,12 +177,12 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
setSearchKeyword('');
|
||||
setSelectedItemType('all');
|
||||
|
||||
// 개별 품목인 경우 선택된 품목 코드 설정
|
||||
// 개별 품목인 경우 선택된 품목 ID 설정
|
||||
if (editRule.registrationType === 'individual') {
|
||||
const codes = editRule.conditionValue.split(',').filter(Boolean);
|
||||
setSelectedItemCodes(new Set(codes));
|
||||
const ids = editRule.conditionValue.split(',').filter(Boolean);
|
||||
setSelectedItemIds(new Set(ids));
|
||||
} else {
|
||||
setSelectedItemCodes(new Set());
|
||||
setSelectedItemIds(new Set());
|
||||
}
|
||||
} else {
|
||||
// 추가 모드: 초기화
|
||||
@@ -167,7 +195,7 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
setIsActive(true);
|
||||
setSearchKeyword('');
|
||||
setSelectedItemType('all');
|
||||
setSelectedItemCodes(new Set());
|
||||
setSelectedItemIds(new Set());
|
||||
}
|
||||
}
|
||||
}, [open, editRule]);
|
||||
@@ -179,7 +207,7 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (selectedItemCodes.size === 0) {
|
||||
if (selectedItemIds.size === 0) {
|
||||
alert('품목을 최소 1개 이상 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
@@ -188,7 +216,7 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
// 개별 품목의 경우 conditionValue에 품목코드들을 저장
|
||||
const finalConditionValue =
|
||||
registrationType === 'individual'
|
||||
? Array.from(selectedItemCodes).join(',')
|
||||
? Array.from(selectedItemIds).join(',')
|
||||
: conditionValue.trim();
|
||||
|
||||
onAdd({
|
||||
@@ -211,7 +239,7 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
setIsActive(true);
|
||||
setSearchKeyword('');
|
||||
setSelectedItemType('all');
|
||||
setSelectedItemCodes(new Set());
|
||||
setSelectedItemIds(new Set());
|
||||
|
||||
onOpenChange(false);
|
||||
};
|
||||
@@ -389,7 +417,7 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
{isItemsLoading ? (
|
||||
'로딩 중...'
|
||||
) : (
|
||||
<>품목 목록 ({itemList.length}개) | 선택됨 ({selectedItemCodes.size}개)</>
|
||||
<>품목 목록 ({itemList.length}개) | 선택됨 ({selectedItemIds.size}개)</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -411,7 +439,7 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
size="sm"
|
||||
onClick={handleResetSelection}
|
||||
className="text-xs h-7"
|
||||
disabled={selectedItemCodes.size === 0}
|
||||
disabled={selectedItemIds.size === 0}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
@@ -439,7 +467,9 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
) : itemList.length === 0 ? (
|
||||
<TableRow key="empty">
|
||||
<TableCell colSpan={4} className="text-center text-muted-foreground py-8">
|
||||
검색 결과가 없습니다
|
||||
{searchKeyword.trim() === ''
|
||||
? '품목을 검색해주세요 (한글 1자 이상, 영문 2자 이상)'
|
||||
: '검색 결과가 없습니다'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
@@ -447,12 +477,12 @@ export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProp
|
||||
<TableRow
|
||||
key={item.id}
|
||||
className="cursor-pointer hover:bg-muted/50"
|
||||
onClick={() => handleToggleItem(item.code)}
|
||||
onClick={() => handleToggleItem(item.id)}
|
||||
>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
checked={selectedItemCodes.has(item.code)}
|
||||
onCheckedChange={() => handleToggleItem(item.code)}
|
||||
checked={selectedItemIds.has(item.id)}
|
||||
onCheckedChange={() => handleToggleItem(item.id)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user