feat: API 통합을 위한 기초 인프라 구축
- 네트워크 레이어 구현 (Dio 기반 ApiClient) - 환경별 설정 관리 시스템 구축 - 의존성 주입 설정 (GetIt) - API 엔드포인트 상수 정의 - 인터셉터 구현 (Auth, Error, Logging) - 프로젝트 아키텍처 개선 (core, data, di 디렉토리 구조) - API 통합 계획서 및 요구사항 문서 작성 - 필요 패키지 추가 (dio, flutter_secure_storage, get_it 등)
This commit is contained in:
146
lib/core/utils/formatters.dart
Normal file
146
lib/core/utils/formatters.dart
Normal 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!;
|
||||
}
|
||||
}
|
||||
154
lib/core/utils/validators.dart
Normal file
154
lib/core/utils/validators.dart
Normal file
@@ -0,0 +1,154 @@
|
||||
import '../constants/app_constants.dart';
|
||||
|
||||
/// 유효성 검사 유틸리티 클래스
|
||||
class Validators {
|
||||
/// 이메일 유효성 검사
|
||||
static String? validateEmail(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '이메일을 입력해주세요.';
|
||||
}
|
||||
|
||||
if (!AppConstants.emailRegex.hasMatch(value)) {
|
||||
return '올바른 이메일 형식이 아닙니다.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 비밀번호 유효성 검사
|
||||
static String? validatePassword(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '비밀번호를 입력해주세요.';
|
||||
}
|
||||
|
||||
if (value.length < 8) {
|
||||
return '비밀번호는 8자 이상이어야 합니다.';
|
||||
}
|
||||
|
||||
if (!value.contains(RegExp(r'[0-9]'))) {
|
||||
return '비밀번호는 숫자를 포함해야 합니다.';
|
||||
}
|
||||
|
||||
if (!value.contains(RegExp(r'[a-zA-Z]'))) {
|
||||
return '비밀번호는 영문자를 포함해야 합니다.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 필수 입력 검사
|
||||
static String? validateRequired(String? value, String fieldName) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return '$fieldName을(를) 입력해주세요.';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 전화번호 유효성 검사
|
||||
static String? validatePhone(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '전화번호를 입력해주세요.';
|
||||
}
|
||||
|
||||
final cleanedValue = value.replaceAll('-', '');
|
||||
if (!AppConstants.phoneRegex.hasMatch(cleanedValue)) {
|
||||
return '올바른 전화번호 형식이 아닙니다.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 사업자번호 유효성 검사
|
||||
static String? validateBusinessNumber(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '사업자번호를 입력해주세요.';
|
||||
}
|
||||
|
||||
final cleanedValue = value.replaceAll('-', '');
|
||||
if (!AppConstants.businessNumberRegex.hasMatch(cleanedValue)) {
|
||||
return '올바른 사업자번호 형식이 아닙니다.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 숫자 유효성 검사
|
||||
static String? validateNumber(String? value, {
|
||||
required String fieldName,
|
||||
int? min,
|
||||
int? max,
|
||||
}) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '$fieldName을(를) 입력해주세요.';
|
||||
}
|
||||
|
||||
final number = int.tryParse(value);
|
||||
if (number == null) {
|
||||
return '숫자만 입력 가능합니다.';
|
||||
}
|
||||
|
||||
if (min != null && number < min) {
|
||||
return '$fieldName은(는) $min 이상이어야 합니다.';
|
||||
}
|
||||
|
||||
if (max != null && number > max) {
|
||||
return '$fieldName은(는) $max 이하여야 합니다.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 날짜 범위 유효성 검사
|
||||
static String? validateDateRange(
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
) {
|
||||
if (startDate == null || endDate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (startDate.isAfter(endDate)) {
|
||||
return '시작일은 종료일보다 이전이어야 합니다.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 파일 확장자 유효성 검사
|
||||
static String? validateFileExtension(String fileName) {
|
||||
final extension = fileName.split('.').last.toLowerCase();
|
||||
|
||||
if (!AppConstants.allowedFileExtensions.contains(extension)) {
|
||||
return '허용되지 않는 파일 형식입니다. (${AppConstants.allowedFileExtensions.join(', ')})';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 파일 크기 유효성 검사
|
||||
static String? validateFileSize(int sizeInBytes) {
|
||||
if (sizeInBytes > AppConstants.maxFileSize) {
|
||||
final maxSizeMB = AppConstants.maxFileSize / (1024 * 1024);
|
||||
return '파일 크기는 ${maxSizeMB}MB를 초과할 수 없습니다.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 시리얼 번호 유효성 검사
|
||||
static String? validateSerialNumber(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return null; // 시리얼 번호는 선택사항
|
||||
}
|
||||
|
||||
if (value.length < 5) {
|
||||
return '시리얼 번호는 5자 이상이어야 합니다.';
|
||||
}
|
||||
|
||||
if (!RegExp(r'^[A-Za-z0-9-]+$').hasMatch(value)) {
|
||||
return '시리얼 번호는 영문, 숫자, 하이픈(-)만 사용 가능합니다.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user