사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/// 주소 관련 상수 및 레이블 정의 파일
|
||||
///
|
||||
/// 한국의 시/도(광역시/도) 및 주소 입력 UI 레이블을 구분하여 관리합니다.
|
||||
library;
|
||||
|
||||
/// 한국의 시/도(광역시/도) 상수 클래스 (불변성 보장)
|
||||
class KoreanRegions {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
/// 앱 전역에서 사용하는 상수 정의 파일
|
||||
///
|
||||
/// 라우트, 장비 상태, 장비 유형, 사용자 권한 등 도메인별로 구분하여 관리합니다.
|
||||
library;
|
||||
|
||||
/// 라우트 이름 상수 클래스
|
||||
class Routes {
|
||||
static const String home = '/';
|
||||
static const String vendor = '/vendor'; // 벤더 관리
|
||||
static const String vendors = '/vendor'; // 복수형 별칭
|
||||
static const String model = '/model'; // 모델 관리
|
||||
static const String models = '/model'; // 복수형 별칭
|
||||
static const String equipment = '/equipment'; // 통합 장비 관리
|
||||
static const String equipmentIn = '/equipment-in'; // 입고 목록(미사용)
|
||||
static const String equipmentInAdd = '/equipment-in/add'; // 장비 입고 폼
|
||||
@@ -23,16 +28,37 @@ class Routes {
|
||||
static const String users = '/user'; // 복수형 별칭
|
||||
static const String userAdd = '/user/add';
|
||||
static const String userEdit = '/user/edit';
|
||||
static const String license = '/license';
|
||||
static const String licenses = '/license'; // 복수형 별칭
|
||||
static const String licenseAdd = '/license/add';
|
||||
static const String licenseEdit = '/license/edit';
|
||||
// License 시스템이 Maintenance로 대체됨
|
||||
static const String warehouseLocation = '/warehouse-location'; // 입고지 관리 목록
|
||||
static const String warehouseLocations = '/warehouse-location'; // 복수형 별칭
|
||||
static const String warehouseLocationAdd =
|
||||
'/warehouse-location/add'; // 입고지 추가
|
||||
static const String warehouseLocationEdit =
|
||||
'/warehouse-location/edit'; // 입고지 수정
|
||||
static const String zipcode = '/zipcode'; // 우편번호 검색
|
||||
static const String zipcodes = '/zipcode'; // 복수형 별칭
|
||||
|
||||
// 재고 관리 라우트
|
||||
static const String inventory = '/inventory'; // 재고 이력
|
||||
static const String inventoryHistory = '/inventory/history'; // 재고 이력
|
||||
static const String inventoryDashboard = '/inventory/dashboard'; // 재고 대시보드
|
||||
static const String inventoryStockIn = '/inventory/stock-in'; // 입고 등록
|
||||
static const String inventoryStockOut = '/inventory/stock-out'; // 출고 처리
|
||||
|
||||
// 유지보수 관리 라우트
|
||||
static const String maintenance = '/maintenance'; // 유지보수 일정
|
||||
static const String maintenanceSchedule = '/maintenance/schedule'; // 유지보수 일정
|
||||
static const String maintenanceAlert = '/maintenance/alert'; // 유지보수 알림
|
||||
static const String maintenanceHistory = '/maintenance/history'; // 유지보수 이력
|
||||
static const String maintenanceAdd = '/maintenance/add'; // 유지보수 추가
|
||||
static const String maintenanceEdit = '/maintenance/edit'; // 유지보수 수정
|
||||
|
||||
// 임대 관리 라우트
|
||||
static const String rent = '/rent'; // 임대 목록
|
||||
static const String rents = '/rent'; // 복수형 별칭
|
||||
static const String rentDashboard = '/rent/dashboard'; // 임대 대시보드
|
||||
static const String rentAdd = '/rent/add'; // 임대 추가
|
||||
static const String rentEdit = '/rent/edit'; // 임대 수정
|
||||
}
|
||||
|
||||
/// 장비 상태 코드 상수 클래스
|
||||
@@ -59,3 +85,10 @@ class UserRoles {
|
||||
static const String admin = 'S'; // 관리자
|
||||
static const String member = 'M'; // 멤버
|
||||
}
|
||||
|
||||
/// 페이지네이션 상수 클래스
|
||||
class PaginationConstants {
|
||||
static const int defaultPageSize = 10; // 기본 페이지 사이즈
|
||||
static const int maxPageSize = 100; // 최대 페이지 사이즈
|
||||
static const int minPageSize = 5; // 최소 페이지 사이즈
|
||||
}
|
||||
|
||||
168
lib/utils/formatters/business_number_formatter.dart
Normal file
168
lib/utils/formatters/business_number_formatter.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// 한국 사업자 번호 자동 포맷팅 (000-00-00000)
|
||||
class BusinessNumberFormatter extends TextInputFormatter {
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
// 숫자만 추출
|
||||
final digitsOnly = newValue.text.replaceAll(RegExp(r'[^\d]'), '');
|
||||
|
||||
// 최대 10자리 제한 (000-00-00000 = 10자리)
|
||||
final truncated = digitsOnly.length > 10
|
||||
? digitsOnly.substring(0, 10)
|
||||
: digitsOnly;
|
||||
|
||||
// 포맷팅
|
||||
String formatted = '';
|
||||
for (int i = 0; i < truncated.length; i++) {
|
||||
if ((i == 3 || i == 5) && i < truncated.length) {
|
||||
formatted += '-';
|
||||
}
|
||||
formatted += truncated[i];
|
||||
}
|
||||
|
||||
// 커서 위치 계산
|
||||
int cursorPosition = formatted.length;
|
||||
|
||||
// 백스페이스 처리: 하이픈 앞에서 백스페이스를 누르면 하이픈도 함께 삭제
|
||||
if (oldValue.text.length > newValue.text.length) {
|
||||
if (newValue.selection.baseOffset == 4 || newValue.selection.baseOffset == 7) {
|
||||
formatted = formatted.substring(0, formatted.length - 1);
|
||||
cursorPosition = formatted.length;
|
||||
}
|
||||
}
|
||||
|
||||
// 입력 중 커서 위치 조정
|
||||
if (newValue.selection.baseOffset < newValue.text.length) {
|
||||
final beforeCursor = newValue.text.substring(0, newValue.selection.baseOffset);
|
||||
final digitsBeforeCursor = beforeCursor.replaceAll(RegExp(r'[^\d]'), '').length;
|
||||
|
||||
cursorPosition = 0;
|
||||
int digitCount = 0;
|
||||
for (int i = 0; i < formatted.length; i++) {
|
||||
if (formatted[i] != '-') {
|
||||
digitCount++;
|
||||
}
|
||||
cursorPosition++;
|
||||
if (digitCount == digitsBeforeCursor) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: cursorPosition),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 사업자 번호 유효성 검증
|
||||
class BusinessNumberValidator {
|
||||
/// 사업자 번호 체크섬 검증
|
||||
/// 대한민국 사업자등록번호 검증 알고리즘 사용
|
||||
static bool isValid(String businessNumber) {
|
||||
// 하이픈 제거
|
||||
final digitsOnly = businessNumber.replaceAll(RegExp(r'[^\d]'), '');
|
||||
|
||||
// 10자리가 아니면 무효
|
||||
if (digitsOnly.length != 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 체크섬 계산을 위한 가중치
|
||||
const weights = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
||||
|
||||
int sum = 0;
|
||||
for (int i = 0; i < 9; i++) {
|
||||
sum += int.parse(digitsOnly[i]) * weights[i];
|
||||
}
|
||||
|
||||
// 9번째 자리(5)에 대한 추가 계산
|
||||
sum += (int.parse(digitsOnly[8]) * 5) ~/ 10;
|
||||
|
||||
// 체크섬 계산
|
||||
final checksum = (10 - (sum % 10)) % 10;
|
||||
|
||||
// 마지막 자리와 체크섬 비교
|
||||
return checksum == int.parse(digitsOnly[9]);
|
||||
}
|
||||
|
||||
/// 사업자 번호 유효성 검증 (폼 필드용)
|
||||
static String? validate(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '사업자 번호를 입력하세요';
|
||||
}
|
||||
|
||||
final digitsOnly = value.replaceAll(RegExp(r'[^\d]'), '');
|
||||
|
||||
if (digitsOnly.length != 10) {
|
||||
return '사업자 번호는 10자리여야 합니다';
|
||||
}
|
||||
|
||||
if (!isValid(value)) {
|
||||
return '유효하지 않은 사업자 번호입니다';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 사업자 번호 타입 추출
|
||||
static String getBusinessType(String businessNumber) {
|
||||
final digitsOnly = businessNumber.replaceAll(RegExp(r'[^\d]'), '');
|
||||
|
||||
if (digitsOnly.length < 5) return '알 수 없음';
|
||||
|
||||
final typeCode = digitsOnly.substring(3, 5);
|
||||
final typeNum = int.parse(typeCode);
|
||||
|
||||
// 사업자 유형 분류 (국세청 기준)
|
||||
if (typeNum >= 1 && typeNum <= 79) {
|
||||
return '개인사업자';
|
||||
} else if (typeNum >= 80 && typeNum <= 89) {
|
||||
return '법인사업자';
|
||||
} else if (typeNum >= 90 && typeNum <= 99) {
|
||||
return '기타';
|
||||
}
|
||||
|
||||
return '알 수 없음';
|
||||
}
|
||||
|
||||
/// 지역 코드 추출 (첫 3자리 기준)
|
||||
static String? getRegionFromBusinessNumber(String businessNumber) {
|
||||
final digitsOnly = businessNumber.replaceAll(RegExp(r'[^\d]'), '');
|
||||
|
||||
if (digitsOnly.length < 3) return null;
|
||||
|
||||
final regionCode = digitsOnly.substring(0, 3);
|
||||
final code = int.parse(regionCode);
|
||||
|
||||
// 국세청 지역 코드 매핑 (주요 지역만)
|
||||
if (code >= 101 && code <= 115) return '서울 중부';
|
||||
if (code >= 116 && code <= 123) return '서울 동부';
|
||||
if (code >= 124 && code <= 133) return '서울 서부';
|
||||
if (code >= 134 && code <= 139) return '서울 남부';
|
||||
if (code >= 140 && code <= 149) return '서울 북부';
|
||||
if (code >= 201 && code <= 209) return '부산';
|
||||
if (code >= 210 && code <= 219) return '인천';
|
||||
if (code >= 220 && code <= 229) return '경기 북부';
|
||||
if (code >= 230 && code <= 239) return '경기 남부';
|
||||
if (code >= 240 && code <= 249) return '강원';
|
||||
if (code >= 301 && code <= 309) return '대전';
|
||||
if (code >= 310 && code <= 319) return '충남';
|
||||
if (code >= 320 && code <= 329) return '충북';
|
||||
if (code >= 401 && code <= 409) return '광주';
|
||||
if (code >= 410 && code <= 419) return '전남';
|
||||
if (code >= 420 && code <= 429) return '전북';
|
||||
if (code >= 501 && code <= 509) return '대구';
|
||||
if (code >= 510 && code <= 519) return '경북';
|
||||
if (code >= 601 && code <= 609) return '울산';
|
||||
if (code >= 610 && code <= 619) return '경남';
|
||||
if (code >= 701 && code <= 709) return '제주';
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
130
lib/utils/formatters/korean_phone_formatter.dart
Normal file
130
lib/utils/formatters/korean_phone_formatter.dart
Normal file
@@ -0,0 +1,130 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// 한국 전화번호 자동 포맷팅 (010-0000-0000)
|
||||
class KoreanPhoneFormatter extends TextInputFormatter {
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
// 숫자만 추출
|
||||
final digitsOnly = newValue.text.replaceAll(RegExp(r'[^\d]'), '');
|
||||
|
||||
// 최대 11자리 제한 (010-0000-0000 = 11자리)
|
||||
final truncated = digitsOnly.length > 11
|
||||
? digitsOnly.substring(0, 11)
|
||||
: digitsOnly;
|
||||
|
||||
// 포맷팅
|
||||
String formatted = '';
|
||||
for (int i = 0; i < truncated.length; i++) {
|
||||
if ((i == 3 || i == 7) && i < truncated.length) {
|
||||
formatted += '-';
|
||||
}
|
||||
formatted += truncated[i];
|
||||
}
|
||||
|
||||
// 커서 위치 계산
|
||||
int cursorPosition = formatted.length;
|
||||
|
||||
// 백스페이스 처리: 하이픈 앞에서 백스페이스를 누르면 하이픈도 함께 삭제
|
||||
if (oldValue.text.length > newValue.text.length) {
|
||||
if (newValue.selection.baseOffset == 3 || newValue.selection.baseOffset == 8) {
|
||||
formatted = formatted.substring(0, formatted.length - 1);
|
||||
cursorPosition = formatted.length;
|
||||
}
|
||||
}
|
||||
|
||||
// 입력 중 커서 위치 조정
|
||||
if (newValue.selection.baseOffset < newValue.text.length) {
|
||||
// 사용자가 중간에 입력하는 경우
|
||||
final beforeCursor = newValue.text.substring(0, newValue.selection.baseOffset);
|
||||
final digitsBeforeCursor = beforeCursor.replaceAll(RegExp(r'[^\d]'), '').length;
|
||||
|
||||
cursorPosition = 0;
|
||||
int digitCount = 0;
|
||||
for (int i = 0; i < formatted.length; i++) {
|
||||
if (formatted[i] != '-') {
|
||||
digitCount++;
|
||||
}
|
||||
cursorPosition++;
|
||||
if (digitCount == digitsBeforeCursor) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: cursorPosition),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 전화번호 유효성 검증
|
||||
class PhoneValidator {
|
||||
static String? validate(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '전화번호를 입력하세요';
|
||||
}
|
||||
|
||||
// 하이픈 제거 후 검증
|
||||
final digitsOnly = value.replaceAll(RegExp(r'[^\d]'), '');
|
||||
|
||||
// 한국 전화번호 패턴
|
||||
// 휴대폰: 010, 011, 016, 017, 018, 019
|
||||
// 일반전화: 02, 031, 032, 033, 041, 042, 043, 044, 051, 052, 053, 054, 055, 061, 062, 063, 064
|
||||
final mobilePattern = RegExp(r'^01[0-9]\d{7,8}$');
|
||||
final landlinePattern = RegExp(r'^0(2|3[1-3]|4[1-4]|5[1-5]|6[1-4])\d{7,8}$');
|
||||
|
||||
if (!mobilePattern.hasMatch(digitsOnly) && !landlinePattern.hasMatch(digitsOnly)) {
|
||||
return '올바른 전화번호 형식이 아닙니다';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 전화번호에서 지역 코드 추출
|
||||
static String? extractAreaCode(String phoneNumber) {
|
||||
final digitsOnly = phoneNumber.replaceAll(RegExp(r'[^\d]'), '');
|
||||
|
||||
// 서울
|
||||
if (digitsOnly.startsWith('02')) return '서울';
|
||||
// 경기
|
||||
if (digitsOnly.startsWith('031')) return '경기';
|
||||
// 인천
|
||||
if (digitsOnly.startsWith('032')) return '인천';
|
||||
// 강원
|
||||
if (digitsOnly.startsWith('033')) return '강원';
|
||||
// 충남
|
||||
if (digitsOnly.startsWith('041')) return '충남';
|
||||
// 대전
|
||||
if (digitsOnly.startsWith('042')) return '대전';
|
||||
// 충북
|
||||
if (digitsOnly.startsWith('043')) return '충북';
|
||||
// 세종
|
||||
if (digitsOnly.startsWith('044')) return '세종';
|
||||
// 부산
|
||||
if (digitsOnly.startsWith('051')) return '부산';
|
||||
// 울산
|
||||
if (digitsOnly.startsWith('052')) return '울산';
|
||||
// 대구
|
||||
if (digitsOnly.startsWith('053')) return '대구';
|
||||
// 경북
|
||||
if (digitsOnly.startsWith('054')) return '경북';
|
||||
// 경남
|
||||
if (digitsOnly.startsWith('055')) return '경남';
|
||||
// 전남
|
||||
if (digitsOnly.startsWith('061')) return '전남';
|
||||
// 광주
|
||||
if (digitsOnly.startsWith('062')) return '광주';
|
||||
// 전북
|
||||
if (digitsOnly.startsWith('063')) return '전북';
|
||||
// 제주
|
||||
if (digitsOnly.startsWith('064')) return '제주';
|
||||
// 휴대폰
|
||||
if (digitsOnly.startsWith('01')) return '휴대폰';
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
204
lib/utils/formatters/number_formatter.dart
Normal file
204
lib/utils/formatters/number_formatter.dart
Normal file
@@ -0,0 +1,204 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
/// 한국식 숫자 포맷팅 유틸리티
|
||||
class KoreanNumberFormatter {
|
||||
static final _currencyFormat = NumberFormat.currency(
|
||||
locale: 'ko_KR',
|
||||
symbol: '₩',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
|
||||
static final _decimalFormat = NumberFormat.decimalPattern('ko_KR');
|
||||
|
||||
/// 통화 포맷팅 (₩1,234,567)
|
||||
static String formatCurrency(num amount) {
|
||||
return _currencyFormat.format(amount);
|
||||
}
|
||||
|
||||
/// 통화 포맷팅 - 심플 (1,234,567원)
|
||||
static String formatCurrencySimple(num amount) {
|
||||
return '${_decimalFormat.format(amount)}원';
|
||||
}
|
||||
|
||||
/// 수량 포맷팅 (1,234개)
|
||||
static String formatQuantity(int quantity, {String unit = '개'}) {
|
||||
return '${_decimalFormat.format(quantity)}$unit';
|
||||
}
|
||||
|
||||
/// 일반 숫자 포맷팅 (1,234,567)
|
||||
static String formatNumber(num number) {
|
||||
return _decimalFormat.format(number);
|
||||
}
|
||||
|
||||
/// 퍼센트 포맷팅 (85.2%)
|
||||
static String formatPercent(double value, {int decimalDigits = 1}) {
|
||||
final formatter = NumberFormat.percentPattern('ko_KR')
|
||||
..minimumFractionDigits = decimalDigits
|
||||
..maximumFractionDigits = decimalDigits;
|
||||
return formatter.format(value);
|
||||
}
|
||||
|
||||
/// 파일 크기 포맷팅 (1.5MB, 256KB 등)
|
||||
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(2)}GB';
|
||||
}
|
||||
}
|
||||
|
||||
/// 한국식 단위 포맷팅 (만, 억, 조)
|
||||
static String formatKoreanUnit(num number) {
|
||||
final abs = number.abs();
|
||||
final isNegative = number < 0;
|
||||
String result;
|
||||
|
||||
if (abs >= 1000000000000) {
|
||||
// 조 단위
|
||||
result = '${(abs / 1000000000000).toStringAsFixed(1)}조';
|
||||
} else if (abs >= 100000000) {
|
||||
// 억 단위
|
||||
result = '${(abs / 100000000).toStringAsFixed(1)}억';
|
||||
} else if (abs >= 10000) {
|
||||
// 만 단위
|
||||
result = '${(abs / 10000).toStringAsFixed(1)}만';
|
||||
} else {
|
||||
result = _decimalFormat.format(abs);
|
||||
}
|
||||
|
||||
return isNegative ? '-$result' : result;
|
||||
}
|
||||
|
||||
/// 기간 포맷팅 (3개월, 1년 2개월 등)
|
||||
static String formatPeriod(int months) {
|
||||
if (months == 0) return '0개월';
|
||||
|
||||
final years = months ~/ 12;
|
||||
final remainingMonths = months % 12;
|
||||
|
||||
if (years == 0) {
|
||||
return '$remainingMonths개월';
|
||||
} else if (remainingMonths == 0) {
|
||||
return '$years년';
|
||||
} else {
|
||||
return '$years년 $remainingMonths개월';
|
||||
}
|
||||
}
|
||||
|
||||
/// 남은 일수 포맷팅
|
||||
static String formatRemainingDays(int days) {
|
||||
if (days < 0) {
|
||||
return '${-days}일 지남';
|
||||
} else if (days == 0) {
|
||||
return '오늘';
|
||||
} else if (days == 1) {
|
||||
return '내일';
|
||||
} else if (days <= 7) {
|
||||
return '$days일 남음';
|
||||
} else if (days <= 30) {
|
||||
final weeks = days ~/ 7;
|
||||
return '$weeks주 남음';
|
||||
} else if (days <= 365) {
|
||||
final months = days ~/ 30;
|
||||
return '$months개월 남음';
|
||||
} else {
|
||||
final years = days ~/ 365;
|
||||
return '$years년 남음';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 숫자 입력 필드용 TextInputFormatter
|
||||
class ThousandsSeparatorInputFormatter extends TextInputFormatter {
|
||||
final String separator;
|
||||
|
||||
ThousandsSeparatorInputFormatter({this.separator = ','});
|
||||
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
// 숫자만 추출
|
||||
final digitsOnly = newValue.text.replaceAll(RegExp(r'[^\d]'), '');
|
||||
|
||||
if (digitsOnly.isEmpty) {
|
||||
return const TextEditingValue();
|
||||
}
|
||||
|
||||
// 천 단위 구분자 추가
|
||||
final formatted = _addThousandsSeparator(digitsOnly);
|
||||
|
||||
// 커서 위치 계산
|
||||
int cursorPosition = formatted.length;
|
||||
|
||||
// 입력 중 커서 위치 조정
|
||||
if (newValue.selection.baseOffset < newValue.text.length) {
|
||||
final beforeCursor = newValue.text.substring(0, newValue.selection.baseOffset);
|
||||
final digitsBeforeCursor = beforeCursor.replaceAll(RegExp(r'[^\d]'), '').length;
|
||||
|
||||
cursorPosition = 0;
|
||||
int digitCount = 0;
|
||||
for (int i = 0; i < formatted.length; i++) {
|
||||
if (formatted[i] != separator) {
|
||||
digitCount++;
|
||||
}
|
||||
cursorPosition++;
|
||||
if (digitCount == digitsBeforeCursor) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: cursorPosition),
|
||||
);
|
||||
}
|
||||
|
||||
String _addThousandsSeparator(String digitsOnly) {
|
||||
final buffer = StringBuffer();
|
||||
final length = digitsOnly.length;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
buffer.write(digitsOnly[i]);
|
||||
|
||||
final remaining = length - i - 1;
|
||||
if (remaining > 0 && remaining % 3 == 0) {
|
||||
buffer.write(separator);
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/// 통화 입력 필드용 TextInputFormatter
|
||||
class CurrencyInputFormatter extends TextInputFormatter {
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
// 숫자만 추출
|
||||
final digitsOnly = newValue.text.replaceAll(RegExp(r'[^\d]'), '');
|
||||
|
||||
if (digitsOnly.isEmpty) {
|
||||
return const TextEditingValue();
|
||||
}
|
||||
|
||||
// 천 단위 구분자 추가
|
||||
final number = int.tryParse(digitsOnly) ?? 0;
|
||||
final formatted = '₩${KoreanNumberFormatter.formatNumber(number)}';
|
||||
|
||||
return TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: formatted.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// 전화번호 관련 유틸리티 클래스 (SRP, 재사용성, 테스트 용이성 중심)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/// 폼 필드 검증 함수 및 유틸리티 (SRP, 재사용성, 테스트 용이성 중심)
|
||||
library;
|
||||
|
||||
/// 필수 입력값 검증
|
||||
String? validateRequired(String? value, String fieldName) {
|
||||
|
||||
Reference in New Issue
Block a user