lib/features/expense/widgets/
├── expense_form/ # 지출 입력 폼 관련 위젯
│ ├── category_selector_widget.dart
│ ├── status_selector_widget.dart
│ └── amount_input_field.dart
├── search/ # 검색 화면 관련 위젯
│ ├── search_bar_widget.dart
│ ├── empty_search_state.dart
│ ├── no_results_state.dart
│ └── search_result_item.dart
└── common/ # 공통 위젯 (향후 확장)
└── (공통으로 사용되는 위젯들)
- Before: 628줄
- After: 395줄 (-37% 감소)
- 분리된 위젯: 3개
- CategorySelectorWidget (70줄)
- StatusSelectorWidget (85줄)
- AmountInputField (90줄)
- Before: 384줄
- After: 145줄 (-62% 감소)
- 분리된 위젯: 4개
- SearchBarWidget (52줄)
- EmptySearchState (30줄)
- NoResultsState (42줄)
- SearchResultItem (180줄)
import 'package:expense_tracker/features/expense/widgets/expense_form/category_selector_widget.dart';
CategorySelectorWidget(
selectedCategory: _selectedCategory,
onChanged: (category) {
setState(() => _selectedCategory = category);
},
)import 'package:expense_tracker/features/expense/widgets/expense_form/status_selector_widget.dart';
StatusSelectorWidget(
selectedStatus: _selectedStatus,
onChanged: (status) {
setState(() => _selectedStatus = status);
},
)import 'package:expense_tracker/features/expense/widgets/expense_form/amount_input_field.dart';
AmountInputField(
controller: _amountController,
)import 'package:expense_tracker/features/expense/widgets/search/search_bar_widget.dart';
SearchBarWidget(
controller: _searchController,
onChanged: (value) {
setState(() => _searchQuery = value);
},
onClear: () {
setState(() {
_searchController.clear();
_searchQuery = '';
});
},
)import 'package:expense_tracker/features/expense/widgets/search/empty_search_state.dart';
const EmptySearchState()import 'package:expense_tracker/features/expense/widgets/search/no_results_state.dart';
NoResultsState(searchQuery: _searchQuery)import 'package:expense_tracker/features/expense/widgets/search/search_result_item.dart';
SearchResultItem(
expense: expense,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddExpenseScreen(expense: expense),
),
);
},
)지출 추가/수정 화면에서 사용되는 입력 폼 관련 위젯들
- 카테고리 선택
- 감정 상태 선택
- 금액 입력
검색 화면에서 사용되는 위젯들
- 검색바
- 빈 상태 표시
- 검색 결과 없음 표시
- 검색 결과 아이템
여러 화면에서 공통으로 사용되는 위젯들 (향후 확장)
- 공통 버튼
- 공통 다이얼로그
- 공통 로딩 인디케이터 등
- ✅ 50줄 이상의 독립적인 UI
- ✅ 재사용 가능성이 있는 컴포넌트
- ✅ 명확한 책임이 있는 부분
- ✅ 다른 화면에서도 사용될 가능성
*Widget: 재사용 가능한 위젯*Screen: 전체 화면*Item: 리스트 아이템*State: 상태 표시 위젯
- 화면별로 관련 위젯을 그룹화
- 공통 위젯은
common/폴더에 - 특정 기능별로 하위 폴더 생성
widgets/expense_list/
├── expense_card_widget.dart # 지출 카드
├── expense_summary_card.dart # 상단 요약 카드
└── filter_chip_widget.dart # 필터 칩들
widgets/emotion_detail/
├── emotion_summary_card.dart # 감정별 요약 카드
└── expense_list_by_emotion.dart # 감정별 지출 목록
widgets/statistics/
├── pie_chart_widget.dart # 파이 차트
├── bar_chart_widget.dart # 막대 차트
└── stats_summary_card.dart # 통계 요약 카드
- 단일 책임 원칙: 각 위젯은 하나의 명확한 역할만 수행
- 재사용성: 다른 화면에서도 사용 가능하도록 설계
- 독립성: 외부 의존성 최소화
- 명확한 인터페이스: Props를 통한 명확한 데이터 전달
- 일관된 스타일: 프로젝트 전체에서 일관된 디자인 유지