mhkim-node #412
|
|
@ -0,0 +1,199 @@
|
|||
# [계획서] 카테고리 트리 대분류 추가 모달 - 연속 등록 모드 수정
|
||||
|
||||
> 관련 문서: [맥락노트](./CCA[맥락]-카테고리-연속등록모드.md) | [체크리스트](./CCA[체크]-카테고리-연속등록모드.md)
|
||||
|
||||
## 개요
|
||||
|
||||
기준정보 - 옵션설정 화면에서 트리 구조 카테고리(예: 품목정보 > 재고단위)의 "대분류 추가" 모달이 저장 후 닫히지 않는 버그를 수정합니다.
|
||||
평면 목록용 추가 모달(`CategoryValueAddDialog.tsx`)과 동일한 연속 입력 패턴을 적용합니다.
|
||||
|
||||
---
|
||||
|
||||
## 현재 동작
|
||||
|
||||
- 대분류 추가 모달에서 값 입력 후 "추가" 클릭 시 **값은 정상 저장됨**
|
||||
- 저장 후 **모달이 닫히지 않고** 폼만 초기화됨 (항상 연속 입력 상태)
|
||||
- "연속 입력" 체크박스 UI가 **없음** → 사용자가 모드를 끌 수 없음
|
||||
- 모달을 닫으려면 "닫기" 버튼 또는 외부 클릭을 해야 함
|
||||
|
||||
### 현재 코드 (CategoryValueManagerTree.tsx - handleAdd, 512~530행)
|
||||
|
||||
```tsx
|
||||
if (response.success) {
|
||||
toast.success("카테고리가 추가되었습니다");
|
||||
// 폼 초기화 (모달은 닫지 않고 연속 입력)
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
valueCode: "",
|
||||
valueLabel: "",
|
||||
description: "",
|
||||
color: "",
|
||||
}));
|
||||
setTimeout(() => addNameRef.current?.focus(), 50);
|
||||
await loadTree(true);
|
||||
if (parentValue) {
|
||||
setExpandedNodes((prev) => new Set([...prev, parentValue.valueId]));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 현재 DialogFooter (809~821행)
|
||||
|
||||
```tsx
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button variant="outline" onClick={() => setIsAddModalOpen(false)} ...>
|
||||
닫기
|
||||
</Button>
|
||||
<Button onClick={handleAdd} ...>
|
||||
추가
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 변경 후 동작
|
||||
|
||||
### 1. 기본 동작: 저장 후 모달 닫힘
|
||||
|
||||
- "추가" 클릭 → 저장 성공 → 모달 닫힘 + 트리 새로고침
|
||||
- `CategoryValueAddDialog.tsx`(평면 목록 추가 모달)와 동일한 기본 동작
|
||||
|
||||
### 2. 연속 입력 체크박스 추가
|
||||
|
||||
- DialogFooter 좌측에 "연속 입력" 체크박스 표시
|
||||
- 기본값: 체크 해제 (OFF)
|
||||
- 체크 시: 저장 후 폼만 초기화, 모달 유지, 이름 필드에 포커스
|
||||
- 체크 해제 시: 저장 후 모달 닫힘
|
||||
|
||||
---
|
||||
|
||||
## 시각적 예시
|
||||
|
||||
| 상태 | 연속 입력 체크 | 추가 버튼 클릭 후 |
|
||||
|------|---------------|-----------------|
|
||||
| 기본 (체크 해제) | [ ] 연속 입력 | 저장 → 모달 닫힘 → 트리 갱신 |
|
||||
| 연속 모드 (체크) | [x] 연속 입력 | 저장 → 폼 초기화 → 모달 유지 → 이름 필드 포커스 |
|
||||
|
||||
### 모달 하단 레이아웃 (ScreenModal.tsx 패턴)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ [닫기] [추가] │ ← DialogFooter (버튼만)
|
||||
├─────────────────────────────────────────┤
|
||||
│ [x] 저장 후 계속 입력 (연속 등록 모드) │ ← border-t 구분선 아래 별도 영역
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 아키텍처
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["사용자: '추가' 클릭"] --> B["handleAdd()"]
|
||||
B --> C{"API 호출 성공?"}
|
||||
C -- 실패 --> D["toast.error → 모달 유지"]
|
||||
C -- 성공 --> E["toast.success + loadTree"]
|
||||
E --> F{"continuousAdd?"}
|
||||
F -- true --> G["폼 초기화 + 이름 필드 포커스\n모달 유지"]
|
||||
F -- false --> H["폼 초기화 + 모달 닫힘"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 변경 대상 파일
|
||||
|
||||
| 파일 | 역할 | 변경 내용 |
|
||||
|------|------|----------|
|
||||
| `frontend/components/table-category/CategoryValueManagerTree.tsx` | 트리형 카테고리 값 관리 | 상태 추가, handleAdd 분기, DialogFooter UI |
|
||||
|
||||
- **변경 규모**: 약 20줄 내외 소규모 변경
|
||||
- **참고 파일**: `frontend/components/table-category/CategoryValueAddDialog.tsx` (동일 패턴)
|
||||
|
||||
---
|
||||
|
||||
## 코드 설계
|
||||
|
||||
### 1. 상태 추가 (286행 근처, 모달 상태 선언부)
|
||||
|
||||
```tsx
|
||||
const [continuousAdd, setContinuousAdd] = useState(false);
|
||||
```
|
||||
|
||||
### 2. handleAdd 성공 분기 수정 (512~530행 대체)
|
||||
|
||||
```tsx
|
||||
if (response.success) {
|
||||
toast.success("카테고리가 추가되었습니다");
|
||||
await loadTree(true);
|
||||
if (parentValue) {
|
||||
setExpandedNodes((prev) => new Set([...prev, parentValue.valueId]));
|
||||
}
|
||||
|
||||
if (continuousAdd) {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
valueCode: "",
|
||||
valueLabel: "",
|
||||
description: "",
|
||||
color: "",
|
||||
}));
|
||||
setTimeout(() => addNameRef.current?.focus(), 50);
|
||||
} else {
|
||||
setFormData({ valueCode: "", valueLabel: "", description: "", color: "", isActive: true });
|
||||
setIsAddModalOpen(false);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. DialogFooter + 연속 등록 체크박스 수정 (809~821행 대체)
|
||||
|
||||
DialogFooter는 버튼만 유지하고, 그 아래에 `border-t` 구분선과 체크박스를 별도 영역으로 배치합니다.
|
||||
`ScreenModal.tsx` (1287~1303행) 패턴 그대로입니다.
|
||||
|
||||
```tsx
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setIsAddModalOpen(false)}
|
||||
className="h-9 flex-1 text-sm sm:flex-none"
|
||||
>
|
||||
닫기
|
||||
</Button>
|
||||
<Button onClick={handleAdd} className="h-9 flex-1 text-sm sm:flex-none">
|
||||
추가
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
{/* 연속 등록 모드 체크박스 - ScreenModal.tsx 패턴 */}
|
||||
<div className="border-t px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="tree-continuous-add"
|
||||
checked={continuousAdd}
|
||||
onCheckedChange={(checked) => setContinuousAdd(checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor="tree-continuous-add" className="cursor-pointer text-sm font-normal select-none">
|
||||
저장 후 계속 입력 (연속 등록 모드)
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 예상 문제 및 대응
|
||||
|
||||
`CategoryValueAddDialog.tsx`와 동일한 패턴이므로 별도 예상 문제 없음.
|
||||
|
||||
---
|
||||
|
||||
## 설계 원칙
|
||||
|
||||
- `CategoryValueAddDialog.tsx`(같은 폴더, 같은 목적)의 패턴을 그대로 따름
|
||||
- 기존 수정/삭제 모달 동작은 변경하지 않음
|
||||
- 하위 추가(중분류/소분류) 모달도 동일한 `handleAdd`를 사용하므로 자동 적용
|
||||
- `Checkbox` import는 이미 존재 (24행)하므로 추가 import 불필요
|
||||
- `Label` import는 이미 존재 (53행)하므로 추가 import 불필요
|
||||
- 체크박스 위치/라벨/className 모두 `ScreenModal.tsx` (1287~1303행)과 동일
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# [맥락노트] 카테고리 트리 대분류 추가 모달 - 연속 등록 모드 수정
|
||||
|
||||
> 관련 문서: [계획서](./CCA[계획]-카테고리-연속등록모드.md) | [체크리스트](./CCA[체크]-카테고리-연속등록모드.md)
|
||||
|
||||
---
|
||||
|
||||
## 왜 이 작업을 하는가
|
||||
|
||||
- 기준정보 - 옵션설정에서 트리 구조 카테고리(품목정보 > 재고단위 등)의 "대분류 추가" 모달이 저장 후 닫히지 않음
|
||||
- 연속 등록 모드가 하드코딩되어 항상 ON 상태이고, 끌 수 있는 UI가 없음
|
||||
- 같은 폴더의 평면 목록 모달(`CategoryValueAddDialog.tsx`)은 이미 올바르게 구현되어 있음
|
||||
- 동일 패턴을 적용하여 일관성 확보
|
||||
|
||||
---
|
||||
|
||||
## 핵심 결정 사항과 근거
|
||||
|
||||
### 1. 기본값: 연속 등록 OFF (모달 닫힘)
|
||||
|
||||
- **결정**: `continuousAdd` 초기값을 `false`로 설정
|
||||
- **근거**: 대부분의 사용자는 한 건 추가 후 결과를 확인하려 함. 연속 입력은 선택적 기능
|
||||
|
||||
### 2. 체크박스 위치: DialogFooter 아래, border-t 구분선 별도 영역
|
||||
|
||||
- **결정**: `ScreenModal.tsx` (1287~1303행) 패턴 그대로 적용
|
||||
- **근거**: "기준정보 - 부서관리" 추가 모달과 동일한 디자인. 프로젝트 관행 준수
|
||||
- **대안 검토**: `CategoryValueAddDialog.tsx`는 DialogFooter 안에 체크박스 배치 → 부서 모달과 다른 디자인이므로 기각
|
||||
|
||||
### 3. 라벨: "저장 후 계속 입력 (연속 등록 모드)"
|
||||
|
||||
- **결정**: `ScreenModal.tsx`와 동일한 라벨 텍스트 사용
|
||||
- **근거**: 부서 추가 모달과 동일한 문구로 사용자 혼란 방지
|
||||
|
||||
### 4. localStorage 미사용
|
||||
|
||||
- **결정**: 컴포넌트 state만 사용, localStorage 영속화 안 함
|
||||
- **근거**: `CategoryValueAddDialog.tsx`(같은 폴더 형제 컴포넌트)가 localStorage를 쓰지 않음. `ScreenModal.tsx`는 사용하지만 동적 화면 모달 전용 기능이므로 범위가 다름
|
||||
|
||||
### 5. 수정 대상: handleAdd 함수만
|
||||
|
||||
- **결정**: 저장 성공 분기에서만 `continuousAdd` 체크
|
||||
- **근거**: 실패 시에는 원래대로 모달 유지 + 에러 표시. 분기가 필요한 건 성공 시뿐
|
||||
|
||||
---
|
||||
|
||||
## 관련 파일 위치
|
||||
|
||||
| 구분 | 파일 경로 | 설명 |
|
||||
|------|----------|------|
|
||||
| 수정 대상 | `frontend/components/table-category/CategoryValueManagerTree.tsx` | 트리형 카테고리 값 관리 (대분류/중분류/소분류) |
|
||||
| 참고 패턴 (로직) | `frontend/components/table-category/CategoryValueAddDialog.tsx` | 평면 목록 추가 모달 - continuousAdd 분기 로직 |
|
||||
| 참고 패턴 (UI) | `frontend/components/common/ScreenModal.tsx` | 동적 화면 모달 - 체크박스 위치/라벨/스타일 |
|
||||
|
||||
---
|
||||
|
||||
## 기술 참고
|
||||
|
||||
### 현재 handleAdd 흐름
|
||||
|
||||
```
|
||||
handleAdd() → API 호출 → 성공 시:
|
||||
1. toast.success
|
||||
2. 폼 초기화 (모달 유지 - 하드코딩)
|
||||
3. addNameRef 포커스
|
||||
4. loadTree(true) - 펼침 상태 유지
|
||||
5. parentValue 있으면 해당 노드 펼침
|
||||
```
|
||||
|
||||
### 변경 후 handleAdd 흐름
|
||||
|
||||
```
|
||||
handleAdd() → API 호출 → 성공 시:
|
||||
1. toast.success
|
||||
2. loadTree(true) + parentValue 펼침
|
||||
3. continuousAdd 체크:
|
||||
- true: 폼 초기화 + addNameRef 포커스 (모달 유지)
|
||||
- false: 폼 초기화 + setIsAddModalOpen(false) (모달 닫힘)
|
||||
```
|
||||
|
||||
### import 현황
|
||||
|
||||
- `Checkbox`: 24행에서 이미 import (`@/components/ui/checkbox`)
|
||||
- `Label`: 53행에서 이미 import (`@/components/ui/label`)
|
||||
- 추가 import 불필요
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# [체크리스트] 카테고리 트리 대분류 추가 모달 - 연속 등록 모드 수정
|
||||
|
||||
> 관련 문서: [계획서](./CCA[계획]-카테고리-연속등록모드.md) | [맥락노트](./CCA[맥락]-카테고리-연속등록모드.md)
|
||||
|
||||
---
|
||||
|
||||
## 공정 상태
|
||||
|
||||
- 전체 진행률: **100%** (구현 완료)
|
||||
- 현재 단계: 완료
|
||||
|
||||
---
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
### 1단계: 상태 추가
|
||||
|
||||
- [x] `CategoryValueManagerTree.tsx` 모달 상태 선언부(286행 근처)에 `continuousAdd` 상태 추가
|
||||
|
||||
### 2단계: handleAdd 분기 수정
|
||||
|
||||
- [x] `handleAdd` 성공 분기(512~530행)에서 `continuousAdd` 체크 분기 추가
|
||||
- [x] `continuousAdd === true`: 폼 초기화 + addNameRef 포커스 (모달 유지)
|
||||
- [x] `continuousAdd === false`: 폼 초기화 + `setIsAddModalOpen(false)` (모달 닫힘)
|
||||
|
||||
### 3단계: DialogFooter UI 수정
|
||||
|
||||
- [x] DialogFooter(809~821행)는 버튼만 유지
|
||||
- [x] DialogFooter 아래에 `border-t px-4 py-3` 영역 추가
|
||||
- [x] "저장 후 계속 입력 (연속 등록 모드)" 체크박스 배치
|
||||
- [x] ScreenModal.tsx (1287~1303행) 패턴과 동일한 className/라벨 사용
|
||||
|
||||
### 4단계: 검증
|
||||
|
||||
- [ ] 대분류 추가: 체크 해제 상태에서 추가 → 모달 닫힘 확인
|
||||
- [ ] 대분류 추가: 체크 상태에서 추가 → 모달 유지 + 폼 초기화 + 포커스 확인
|
||||
- [ ] 하위 추가(중분류/소분류): 동일하게 동작하는지 확인
|
||||
- [ ] 수정/삭제 모달: 기존 동작 변화 없음 확인
|
||||
|
||||
### 5단계: 정리
|
||||
|
||||
- [x] 린트 에러 없음 확인
|
||||
- [x] 이 체크리스트 완료 표시 업데이트
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력
|
||||
|
||||
| 날짜 | 내용 |
|
||||
|------|------|
|
||||
| 2026-03-11 | 계획서, 맥락노트, 체크리스트 작성 완료 |
|
||||
| 2026-03-11 | 구현 완료 (1~3단계, 5단계 정리). 4단계 검증은 수동 테스트 필요 |
|
||||
|
|
@ -288,6 +288,7 @@ export const CategoryValueManagerTree: React.FC<CategoryValueManagerTreeProps> =
|
|||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||
const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false);
|
||||
const [parentValue, setParentValue] = useState<CategoryValue | null>(null);
|
||||
const [continuousAdd, setContinuousAdd] = useState(false);
|
||||
const [editingValue, setEditingValue] = useState<CategoryValue | null>(null);
|
||||
const [deletingValue, setDeletingValue] = useState<CategoryValue | null>(null);
|
||||
|
||||
|
|
@ -512,21 +513,24 @@ export const CategoryValueManagerTree: React.FC<CategoryValueManagerTreeProps> =
|
|||
const response = await createCategoryValue(input);
|
||||
if (response.success) {
|
||||
toast.success("카테고리가 추가되었습니다");
|
||||
// 폼 초기화 (모달은 닫지 않고 연속 입력)
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
valueCode: "",
|
||||
valueLabel: "",
|
||||
description: "",
|
||||
color: "",
|
||||
}));
|
||||
setTimeout(() => addNameRef.current?.focus(), 50);
|
||||
// 기존 펼침 상태 유지하면서 데이터 새로고침
|
||||
await loadTree(true);
|
||||
// 부모 노드만 펼치기 (하위 추가 시)
|
||||
if (parentValue) {
|
||||
setExpandedNodes((prev) => new Set([...prev, parentValue.valueId]));
|
||||
}
|
||||
|
||||
if (continuousAdd) {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
valueCode: "",
|
||||
valueLabel: "",
|
||||
description: "",
|
||||
color: "",
|
||||
}));
|
||||
setTimeout(() => addNameRef.current?.focus(), 50);
|
||||
} else {
|
||||
setFormData({ valueCode: "", valueLabel: "", description: "", color: "", isActive: true });
|
||||
setIsAddModalOpen(false);
|
||||
}
|
||||
} else {
|
||||
toast.error(response.error || "추가 실패");
|
||||
}
|
||||
|
|
@ -818,6 +822,19 @@ export const CategoryValueManagerTree: React.FC<CategoryValueManagerTreeProps> =
|
|||
추가
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<div className="border-t px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="tree-continuous-add"
|
||||
checked={continuousAdd}
|
||||
onCheckedChange={(checked) => setContinuousAdd(checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor="tree-continuous-add" className="cursor-pointer text-sm font-normal select-none">
|
||||
저장 후 계속 입력 (연속 등록 모드)
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue