feat(WEB): 공정관리 품목 제거 기능 및 리팩토링

- ProcessDetail: 개별 품목 제거(removeProcessItem) 기능 추가
- ProcessDetail: onProcessUpdate 콜백으로 부모 컴포넌트 동기화
- ProcessDetail: 삭제 다이얼로그 제거, 품목 목록 flatMap 추출 방식 개선
- ProcessForm: 규칙 모달 관련 코드 추가
- RuleModal: UI 개선
- actions.ts: removeProcessItem API 함수 추가
This commit is contained in:
2026-02-09 21:31:00 +09:00
parent 2ad27d738f
commit 6d8116713f
5 changed files with 154 additions and 55 deletions

View File

@@ -106,10 +106,23 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
}
}, [categoryOptions]); // eslint-disable-line react-hooks/exhaustive-deps
// 품목 개수 계산
const itemCount = classificationRules
// 개별 품목 목록 추출
const individualItems = classificationRules
.filter((r) => r.registrationType === 'individual')
.reduce((sum, r) => sum + (r.items?.length || 0), 0);
.flatMap((r) => r.items || []);
const itemCount = individualItems.length;
// 품목 삭제 (로컬 상태에서 제거)
const handleRemoveItem = useCallback((itemId: string) => {
setClassificationRules((prev) =>
prev.map((rule) => {
if (rule.registrationType !== 'individual') return rule;
const filtered = (rule.items || []).filter((item) => item.id !== itemId);
const newCondition = filtered.map((item) => item.id).join(',');
return { ...rule, items: filtered, conditionValue: newCondition };
}).filter((rule) => rule.registrationType !== 'individual' || (rule.items && rule.items.length > 0))
);
}, []);
// 부서 목록 + 단계 목록 로드
useEffect(() => {
@@ -136,13 +149,51 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
}
}, [isEdit, initialData?.id]);
// 품목 규칙 추가/수정
// 이미 등록된 품목 ID 목록 (RuleModal에서 필터링용)
const registeredItemIds = useMemo(() => {
const ids = new Set<string>();
classificationRules
.filter((r) => r.registrationType === 'individual')
.forEach((r) => {
// API에서 로드된 items
(r.items || []).forEach((item) => ids.add(item.id));
// conditionValue (새로 선택된 것 포함)
r.conditionValue.split(',').filter(Boolean).forEach((id) => ids.add(id));
});
return ids;
}, [classificationRules]);
// 품목 규칙 추가/수정 (기존 individual 규칙과 병합하여 중복 방지)
const handleSaveRule = useCallback(
(ruleData: Omit<ClassificationRule, 'id' | 'createdAt'>) => {
if (editingRule) {
setClassificationRules((prev) =>
prev.map((r) => (r.id === editingRule.id ? { ...r, ...ruleData } : r))
);
} else if (ruleData.registrationType === 'individual') {
// 새로 선택된 품목 ID
const newItemIds = ruleData.conditionValue.split(',').filter(Boolean);
setClassificationRules((prev) => {
const existingIndividualRule = prev.find((r) => r.registrationType === 'individual');
if (existingIndividualRule) {
// 기존 individual 규칙에 병합 (중복 제거)
const existingIds = existingIndividualRule.conditionValue.split(',').filter(Boolean);
const mergedIds = [...new Set([...existingIds, ...newItemIds])];
return prev.map((r) =>
r.id === existingIndividualRule.id
? { ...r, conditionValue: mergedIds.join(',') }
: r
);
} else {
// 새 규칙 생성
return [...prev, {
...ruleData,
id: `rule-${Date.now()}`,
createdAt: new Date().toISOString(),
}];
}
});
} else {
const newRule: ClassificationRule = {
...ruleData,
@@ -443,6 +494,28 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
</div>
</div>
</CardHeader>
{individualItems.length > 0 && (
<CardContent className="pt-4">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
{individualItems.map((item) => (
<div
key={item.id}
className="flex items-center gap-2 rounded-md border bg-muted/30 px-3 py-1.5 text-sm"
>
<span className="font-mono text-muted-foreground shrink-0">{item.code}</span>
<span className="truncate flex-1">{item.name}</span>
<button
type="button"
onClick={() => handleRemoveItem(item.id)}
className="shrink-0 ml-auto rounded-sm text-muted-foreground/50 hover:text-destructive transition-colors"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
))}
</div>
</CardContent>
)}
</Card>
{/* 단계 테이블 */}
@@ -593,6 +666,7 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
editRule={editingRule}
processId={initialData?.id}
processName={processName}
registeredItemIds={registeredItemIds}
/>
</>
),
@@ -613,6 +687,9 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
departmentOptions,
isDepartmentsLoading,
itemCount,
individualItems,
registeredItemIds,
handleRemoveItem,
dragIndex,
dragOverIndex,
handleSaveRule,