mhkim-node #412

Merged
kjs merged 25 commits from mhkim-node into main 2026-03-12 08:25:45 +09:00
4 changed files with 363 additions and 11 deletions
Showing only changes of commit 634f0cae18 - Show all commits

View File

@ -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행)과 동일

View File

@ -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 불필요

View File

@ -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단계 검증은 수동 테스트 필요 |

View File

@ -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>