전역 구조 리팩터링 및 테스트 확장

This commit is contained in:
JiWoong Sul
2025-09-29 01:51:47 +09:00
parent c00c0c9ab2
commit fef7108479
70 changed files with 7709 additions and 3185 deletions

105
doc/input_widget_guide.md Normal file
View File

@@ -0,0 +1,105 @@
# 입력 위젯 가이드
Superport v2의 폼 UI는 shadcn_ui 구성 요소를 기반으로 하며, 아래 지침을 따를 때 레이아웃과 상호작용이 일관되게 유지된다.
## 1. 기본 컨테이너 — `SuperportFormField`
- 라벨/필수 표시/보조 설명/에러 메시지를 하나의 빌딩 블록으로 묶는다.
- 라벨은 좌측 정렬, 12pt, 필수 항목은 `*`(파괴색)으로 표시한다.
- 자식 위젯은 `ShadInput`, `ShadSelect`, `SuperportDatePickerButton` 등 어떤 입력 요소든 가능하다.
- 에러 문구는 상단 Validator에서 내려오는 한글 메시지를 그대로 사용한다.
```dart
SuperportFormField(
label: '창고',
required: true,
caption: '입고가 진행될 창고를 선택하세요.',
errorText: state.errorMessage,
child: ShadSelect<String>(
initialValue: controller.selectedWarehouse,
options: warehouses.map((w) => ShadOption(value: w.id, child: Text(w.name))).toList(),
onChanged: controller.onWarehouseChanged,
),
);
```
## 2. 텍스트 입력 — `SuperportTextInput`
- `ShadInput`에 공통 프리셋을 적용한 래퍼.
- 플레이스홀더는 한글 문장형으로 작성하고, 검색 필드라면 돋보기 아이콘을 `leading`으로 배치한다.
- 여러 줄 입력은 `maxLines` 변경만으로 처리한다.
```dart
SuperportFormField(
label: '비고',
child: SuperportTextInput(
controller: remarkController,
placeholder: const Text('추가 설명을 입력하세요.'),
maxLines: 3,
),
);
```
## 3. 선택 컴포넌트 — `ShadSelect`
- 단일 선택은 `ShadSelect<T>`를 그대로 사용하고, `SuperportFormField`로 라벨만 감싼다.
- 다중 선택이 필요한 경우 `ShadSelect.multiple` 과 토큰(UiChip) 스타일을 조합한다.
- 최초 옵션은 `전체`/`선택하세요`처럼 명확한 기본값을 제공한다.
```dart
SuperportFormField(
label: '상태',
required: true,
child: ShadSelect<OrderStatus?>(
initialValue: controller.pendingStatus,
selectedOptionBuilder: (_, value) => Text(value?.label ?? '전체 상태'),
options: [
const ShadOption(value: null, child: Text('전체 상태')),
for (final status in OrderStatus.values)
ShadOption(value: status, child: Text(status.label)),
],
onChanged: controller.onStatusChanged,
),
);
```
## 4. 토글 — `SuperportSwitchField`
- 스위치 단독 사용 시 라벨·캡션 레이아웃을 제공한다.
- 접근성 관점에서 토글 설명은 문장형으로 작성한다.
```dart
SuperportSwitchField(
label: '파트너사 전용',
value: controller.isPartnerOnly,
onChanged: controller.onPartnerOnlyChanged,
caption: '활성화 시 파트너사만 접근할 수 있습니다.',
);
```
## 5. 날짜/기간 — `SuperportDatePickerButton`
- 단일 날짜는 `SuperportDatePickerButton`, 기간은 `SuperportDateRangePickerButton`을 사용한다.
- 포맷은 기본적으로 `yyyy-MM-dd`, 필요 시 `dateFormat`으로 주입.
- 기간 선택은 `firstDate`/`lastDate` 범위를 명시해 엣지 케이스를 제한한다.
```dart
SuperportFormField(
label: '처리 기간',
child: SuperportDateRangePickerButton(
value: controller.pendingRange,
onChanged: controller.onRangeChanged,
firstDate: DateTime(2020),
lastDate: DateTime(2030),
),
);
```
## 6. 검증 메시지
- Validator는 필수 오류 → 형식 오류 → 업무 규칙 순서로 확인하고, 메시지는 `SuperportFormField.errorText`로 전달한다.
- 포커스 이동 시 즉시 에러를 표시하며, 성공 시 `caption`으로 가이드를 남겨 재입력을 돕는다.
## 7. 레이아웃
- 가로 240px/500px 프리셋은 `SizedBox`로 감싸 사용하며, 반응형 환경에서는 `ResponsiveLayoutSlot`(섹션 13 참조)을 이용한다.
- 두 줄 이상 배치 시 `Wrap` + `spacing:16`/`runSpacing:16`을 기본으로 한다.
## 8. 샘플 코드 경로
- `lib/widgets/components/form_field.dart`
- `lib/features/inventory/inbound/presentation/pages/inbound_page.dart` — 입고 등록 모달
위 가이드를 준수하면 폼 간 스타일과 상호작용 규칙을 동일하게 유지할 수 있다.