feat: API 통합을 위한 기초 인프라 구축

- 네트워크 레이어 구현 (Dio 기반 ApiClient)
- 환경별 설정 관리 시스템 구축
- 의존성 주입 설정 (GetIt)
- API 엔드포인트 상수 정의
- 인터셉터 구현 (Auth, Error, Logging)
- 프로젝트 아키텍처 개선 (core, data, di 디렉토리 구조)
- API 통합 계획서 및 요구사항 문서 작성
- 필요 패키지 추가 (dio, flutter_secure_storage, get_it 등)
This commit is contained in:
JiWoong Sul
2025-07-24 14:54:28 +09:00
parent e0bc5894b2
commit 2b31d3af5f
29 changed files with 3542 additions and 344 deletions

View File

@@ -0,0 +1,146 @@
import 'package:intl/intl.dart';
import '../constants/app_constants.dart';
/// 데이터 포맷터 유틸리티 클래스
class Formatters {
/// 날짜 포맷
static String formatDate(DateTime date) {
return DateFormat(AppConstants.dateFormat).format(date);
}
/// 날짜+시간 포맷
static String formatDateTime(DateTime dateTime) {
return DateFormat(AppConstants.dateTimeFormat).format(dateTime);
}
/// 상대적 시간 포맷 (예: 3분 전, 2시간 전)
static String formatRelativeTime(DateTime dateTime) {
final now = DateTime.now();
final difference = now.difference(dateTime);
if (difference.inDays > 365) {
return '${(difference.inDays / 365).floor()}년 전';
} else if (difference.inDays > 30) {
return '${(difference.inDays / 30).floor()}개월 전';
} else if (difference.inDays > 0) {
return '${difference.inDays}일 전';
} else if (difference.inHours > 0) {
return '${difference.inHours}시간 전';
} else if (difference.inMinutes > 0) {
return '${difference.inMinutes}분 전';
} else {
return '방금 전';
}
}
/// 숫자 포맷 (천 단위 구분)
static String formatNumber(int number) {
return NumberFormat('#,###').format(number);
}
/// 통화 포맷
static String formatCurrency(int amount) {
return NumberFormat.currency(
locale: 'ko_KR',
symbol: '',
decimalDigits: 0,
).format(amount);
}
/// 전화번호 포맷
static String formatPhone(String phone) {
final cleaned = phone.replaceAll(RegExp(r'[^0-9]'), '');
if (cleaned.length == 11) {
return '${cleaned.substring(0, 3)}-${cleaned.substring(3, 7)}-${cleaned.substring(7)}';
} else if (cleaned.length == 10) {
if (cleaned.startsWith('02')) {
return '${cleaned.substring(0, 2)}-${cleaned.substring(2, 6)}-${cleaned.substring(6)}';
} else {
return '${cleaned.substring(0, 3)}-${cleaned.substring(3, 6)}-${cleaned.substring(6)}';
}
}
return phone;
}
/// 사업자번호 포맷
static String formatBusinessNumber(String businessNumber) {
final cleaned = businessNumber.replaceAll(RegExp(r'[^0-9]'), '');
if (cleaned.length == 10) {
return '${cleaned.substring(0, 3)}-${cleaned.substring(3, 5)}-${cleaned.substring(5)}';
}
return businessNumber;
}
/// 파일 크기 포맷
static String formatFileSize(int bytes) {
if (bytes < 1024) {
return '$bytes B';
} else if (bytes < 1024 * 1024) {
return '${(bytes / 1024).toStringAsFixed(1)} KB';
} else if (bytes < 1024 * 1024 * 1024) {
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
} else {
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
}
}
/// 백분율 포맷
static String formatPercentage(double value, {int decimals = 0}) {
return '${(value * 100).toStringAsFixed(decimals)}%';
}
/// 기간 포맷 (일수)
static String formatDuration(int days) {
if (days >= 365) {
final years = days ~/ 365;
final remainingDays = days % 365;
if (remainingDays > 0) {
return '$years년 $remainingDays일';
}
return '$years년';
} else if (days >= 30) {
final months = days ~/ 30;
final remainingDays = days % 30;
if (remainingDays > 0) {
return '$months개월 $remainingDays일';
}
return '$months개월';
} else {
return '$days일';
}
}
/// 상태 텍스트 변환
static String formatStatus(String status) {
return AppConstants.equipmentStatus[status] ?? status;
}
/// 역할 텍스트 변환
static String formatRole(String role) {
switch (role) {
case 'S':
case 'admin':
return '관리자';
case 'M':
case 'manager':
return '매니저';
case 'U':
case 'staff':
return '직원';
case 'V':
case 'viewer':
return '열람자';
default:
return role;
}
}
/// null 값 처리
static String formatNullable(String? value, {String defaultValue = '-'}) {
return value?.isEmpty ?? true ? defaultValue : value!;
}
}