Files
superport/.claude/code_patterns_guide.md
JiWoong Sul e0bc5894b2 UI 전체 리디자인 및 개선사항 적용
## 주요 변경사항:

### UI/UX 개선
- shadcn/ui 스타일 기반의 새로운 디자인 시스템 도입
- 모든 주요 화면에 대한 리디자인 구현 완료
  - 로그인 화면: 모던한 카드 스타일 적용
  - 대시보드: 통계 카드와 차트를 활용한 개요 화면
  - 리스트 화면들: 일관된 테이블 디자인과 검색/필터 기능
- 다크모드 지원을 위한 테마 시스템 구축

### 기능 개선
- Equipment List: 고급 필터링 (상태, 담당자별)
- Company List: 검색 및 정렬 기능 강화
- User List: 역할별 필터링 추가
- License List: 만료일 기반 상태 표시
- Warehouse Location: 재고 수준 시각화

### 기술적 개선
- 재사용 가능한 컴포넌트 라이브러리 구축
- 일관된 코드 패턴 가이드라인 작성
- 프로젝트 구조 분석 및 문서화

### 문서화
- 프로젝트 분석 문서 추가
- UI 리디자인 진행 상황 문서
- 코드 패턴 가이드 작성
- Equipment 기능 격차 분석 및 구현 계획

### 삭제/리팩토링
- goods_list.dart 제거 (equipment_list로 통합)
- 불필요한 import 및 코드 정리

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-07 19:45:32 +09:00

502 lines
12 KiB
Markdown

# Superport 코드 패턴 가이드
## 1. 파일 구조 및 네이밍 규칙
### 1.1 디렉토리 구조
```
lib/
├── models/ # 데이터 모델 (접미사: _model.dart)
├── screens/ # 화면 구성
│ ├── common/ # 공통 컴포넌트 및 레이아웃
│ └── [feature]/ # 기능별 디렉토리
├── services/ # 비즈니스 로직 및 데이터 서비스
└── utils/ # 유틸리티 함수 및 상수
```
### 1.2 파일 네이밍 규칙
- **모델**: `entity_name_model.dart` (예: `user_model.dart`)
- **화면**: `feature_screen.dart` (예: `login_screen.dart`)
- **리스트**: `entity_list.dart` (예: `user_list.dart`)
- **폼**: `entity_form_screen.dart` (예: `user_form_screen.dart`)
- **컨트롤러**: `feature_controller.dart` (예: `login_controller.dart`)
- **위젯**: `widget_name.dart` (예: `custom_button.dart`)
## 2. 코드 패턴
### 2.1 모델 클래스 패턴
```dart
class EntityModel {
final String id;
final String name;
final DateTime? createdAt;
EntityModel({
required this.id,
required this.name,
this.createdAt,
});
// copyWith 메서드 필수
EntityModel copyWith({
String? id,
String? name,
DateTime? createdAt,
}) {
return EntityModel(
id: id ?? this.id,
name: name ?? this.name,
createdAt: createdAt ?? this.createdAt,
);
}
// JSON 직렬화 (선택적)
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'createdAt': createdAt?.toIso8601String(),
};
}
```
### 2.2 화면(Screen) 패턴
```dart
class FeatureScreen extends StatefulWidget {
final String? id; // 선택적 파라미터
const FeatureScreen({Key? key, this.id}) : super(key: key);
@override
State<FeatureScreen> createState() => _FeatureScreenState();
}
class _FeatureScreenState extends State<FeatureScreen> {
late final FeatureController _controller;
@override
void initState() {
super.initState();
_controller = FeatureController();
_controller.initialize(widget.id);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _buildBody(),
);
}
}
```
### 2.3 컨트롤러 패턴
```dart
class FeatureController extends ChangeNotifier {
final MockDataService _dataService = MockDataService();
bool _isLoading = false;
String? _error;
List<Model> _items = [];
// Getters
bool get isLoading => _isLoading;
String? get error => _error;
List<Model> get items => _items;
// 초기화
Future<void> initialize() async {
_setLoading(true);
try {
_items = await _dataService.getItems();
} catch (e) {
_error = e.toString();
} finally {
_setLoading(false);
}
}
// 상태 업데이트
void _setLoading(bool value) {
_isLoading = value;
notifyListeners();
}
@override
void dispose() {
// 정리 작업
super.dispose();
}
}
```
### 2.4 리스트 화면 패턴 (리디자인 버전)
```dart
class EntityListRedesign extends StatefulWidget {
const EntityListRedesign({Key? key}) : super(key: key);
@override
State<EntityListRedesign> createState() => _EntityListRedesignState();
}
class _EntityListRedesignState extends State<EntityListRedesign> {
final EntityListController _controller = EntityListController();
@override
Widget build(BuildContext context) {
return AppLayoutRedesign(
currentRoute: Routes.entity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 헤더
_buildHeader(),
SizedBox(height: ShadcnTheme.spacing.lg),
// 컨텐츠
Expanded(
child: ShadcnCard(
padding: EdgeInsets.zero,
child: _buildContent(),
),
),
],
),
);
}
Widget _buildHeader() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${_controller.items.length}',
style: ShadcnTheme.typography.bodyMuted,
),
ShadcnButton(
onPressed: () => _navigateToForm(),
icon: Icons.add,
label: '추가',
),
],
);
}
}
```
### 2.5 폼 화면 패턴
```dart
class EntityFormScreen extends StatefulWidget {
final String? id;
const EntityFormScreen({Key? key, this.id}) : super(key: key);
@override
State<EntityFormScreen> createState() => _EntityFormScreenState();
}
class _EntityFormScreenState extends State<EntityFormScreen> {
final _formKey = GlobalKey<FormState>();
late final EntityFormController _controller;
@override
void initState() {
super.initState();
_controller = EntityFormController(id: widget.id);
_controller.loadData();
}
Future<void> _handleSave() async {
if (!_formKey.currentState!.validate()) return;
_formKey.currentState!.save();
try {
await _controller.save();
if (mounted) {
Navigator.pop(context, true);
}
} catch (e) {
// 에러 처리
}
}
@override
Widget build(BuildContext context) {
return MainLayout(
title: widget.id == null ? '새 항목 추가' : '항목 수정',
showBackButton: true,
child: Form(
key: _formKey,
child: Column(
children: [
// 폼 필드들
],
),
),
);
}
}
```
## 3. 위젯 사용 패턴
### 3.1 shadcn 컴포넌트 사용 (리디자인)
```dart
// 카드
ShadcnCard(
child: Column(
children: [...],
),
);
// 버튼
ShadcnButton(
onPressed: () {},
label: '저장',
variant: ShadcnButtonVariant.primary,
);
// 입력 필드
ShadcnInput(
value: _controller.name,
onChanged: (value) => _controller.name = value,
placeholder: '이름을 입력하세요',
);
// 배지
ShadcnBadge(
label: '활성',
variant: ShadcnBadgeVariant.success,
);
```
### 3.2 테이블/리스트 패턴
```dart
// DataTable 사용
DataTable(
columns: [
DataColumn(label: Text('이름')),
DataColumn(label: Text('상태')),
DataColumn(label: Text('작업')),
],
rows: _controller.items.map((item) => DataRow(
cells: [
DataCell(Text(item.name)),
DataCell(ShadcnBadge(label: item.status)),
DataCell(Row(
children: [
IconButton(
icon: Icon(Icons.edit),
onPressed: () => _handleEdit(item),
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () => _handleDelete(item),
),
],
)),
],
)).toList(),
);
```
## 4. 서비스 레이어 패턴
### 4.1 Mock 데이터 서비스
```dart
class MockDataService {
static final MockDataService _instance = MockDataService._internal();
factory MockDataService() => _instance;
MockDataService._internal();
// 데이터 저장소
final List<Model> _items = [];
// CRUD 메서드
Future<List<Model>> getItems() async {
await Future.delayed(Duration(milliseconds: 300)); // 네트워크 지연 시뮬레이션
return List.from(_items);
}
Future<Model> addItem(Model item) async {
await Future.delayed(Duration(milliseconds: 300));
_items.add(item);
return item;
}
Future<void> updateItem(String id, Model item) async {
await Future.delayed(Duration(milliseconds: 300));
final index = _items.indexWhere((i) => i.id == id);
if (index != -1) {
_items[index] = item;
}
}
Future<void> deleteItem(String id) async {
await Future.delayed(Duration(milliseconds: 300));
_items.removeWhere((i) => i.id == id);
}
}
```
## 5. 유틸리티 패턴
### 5.1 Validator
```dart
class Validators {
static String? required(String? value, {String? fieldName}) {
if (value == null || value.trim().isEmpty) {
return '${fieldName ?? '이 필드'}는 필수입니다.';
}
return null;
}
static String? email(String? value) {
if (value == null || value.isEmpty) return null;
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return '올바른 이메일 형식이 아닙니다.';
}
return null;
}
}
```
### 5.2 상수 정의
```dart
class Routes {
static const String home = '/';
static const String login = '/login';
static const String equipment = '/equipment';
// ...
}
class AppColors {
static const Color primary = Color(0xFF3B82F6);
static const Color secondary = Color(0xFF64748B);
// ...
}
```
## 6. 베스트 프랙티스
### 6.1 일반 규칙
1. **단일 책임 원칙**: 각 클래스/함수는 하나의 책임만 가져야 함
2. **DRY 원칙**: 코드 중복을 피하고 재사용 가능한 컴포넌트 작성
3. **명확한 네이밍**: 변수, 함수, 클래스명은 용도를 명확히 표현
4. **일관성**: 프로젝트 전체에서 동일한 패턴과 스타일 사용
### 6.2 Flutter 특화
1. **const 생성자 사용**: 가능한 모든 위젯에 const 사용
2. **Key 사용**: 리스트나 동적 위젯에는 적절한 Key 제공
3. **BuildContext 주의**: async 작업 후 context 사용 시 mounted 체크
4. **메모리 누수 방지**: Controller, Stream 등은 dispose에서 정리
### 6.3 리디자인 관련
1. **테마 시스템 사용**: 하드코딩된 스타일 대신 ShadcnTheme 사용
2. **컴포넌트 재사용**: shadcn_components의 표준 컴포넌트 활용
3. **일관된 레이아웃**: AppLayoutRedesign으로 모든 화면 감싸기
4. **반응형 디자인**: 다양한 화면 크기 고려
## 7. 코드 예제
### 7.1 완전한 리스트 화면 예제
```dart
// user_list_redesign.dart
class UserListRedesign extends StatefulWidget {
const UserListRedesign({Key? key}) : super(key: key);
@override
State<UserListRedesign> createState() => _UserListRedesignState();
}
class _UserListRedesignState extends State<UserListRedesign> {
final UserListController _controller = UserListController();
@override
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
await _controller.loadUsers();
if (mounted) setState(() {});
}
@override
Widget build(BuildContext context) {
return AppLayoutRedesign(
currentRoute: Routes.user,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 헤더
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${_controller.users.length}',
style: ShadcnTheme.typography.bodyMuted,
),
ShadcnButton(
onPressed: _navigateToAdd,
icon: Icons.add,
label: '사용자 추가',
),
],
),
SizedBox(height: ShadcnTheme.spacing.lg),
// 테이블
Expanded(
child: ShadcnCard(
padding: EdgeInsets.zero,
child: _controller.users.isEmpty
? _buildEmptyState()
: _buildDataTable(),
),
),
],
),
);
}
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.people_outline, size: 64, color: ShadcnTheme.muted),
SizedBox(height: ShadcnTheme.spacing.md),
Text('사용자가 없습니다', style: ShadcnTheme.typography.bodyMuted),
],
),
);
}
Widget _buildDataTable() {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
// 테이블 구현
),
);
}
Future<void> _navigateToAdd() async {
final result = await Navigator.pushNamed(context, Routes.userAdd);
if (result == true) {
_loadData();
}
}
}
```
---
*이 가이드는 Superport 프로젝트의 코드 일관성을 위해 작성되었습니다.*
*마지막 업데이트: 2025-07-07*