- License Expiry Summary API 연동 완료 - 30/60/90일 내 만료 예정 라이선스 요약 표시 - 대시보드 상단에 알림 카드로 통합 - 만료 임박 순서로 색상 구분 (빨강/주황/노랑) - Lookup 데이터 전역 캐싱 시스템 구축 - LookupService 및 RemoteDataSource 생성 - 전체 lookup 데이터 일괄 로드 및 캐싱 - 타입별 필터링 지원 - 새로운 모델 추가 - LicenseExpirySummary (Freezed) - LookupData, LookupCategory, LookupItem 모델 - CLAUDE.md 문서 업데이트 - 미사용 API 활용 계획 추가 - 구현 우선순위 정의 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
165 lines
5.1 KiB
Dart
165 lines
5.1 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:injectable/injectable.dart';
|
|
import 'package:superport/data/datasources/remote/lookup_remote_datasource.dart';
|
|
import 'package:superport/data/models/lookups/lookup_data.dart';
|
|
|
|
@lazySingleton
|
|
class LookupService extends ChangeNotifier {
|
|
final LookupRemoteDataSource _dataSource;
|
|
|
|
LookupData? _lookupData;
|
|
bool _isLoading = false;
|
|
String? _error;
|
|
DateTime? _lastFetchTime;
|
|
|
|
// 캐시 유효 시간 (30분)
|
|
static const Duration _cacheValidDuration = Duration(minutes: 30);
|
|
|
|
LookupService(this._dataSource);
|
|
|
|
// Getters
|
|
LookupData? get lookupData => _lookupData;
|
|
bool get isLoading => _isLoading;
|
|
String? get error => _error;
|
|
bool get hasData => _lookupData != null;
|
|
|
|
// 캐시가 유효한지 확인
|
|
bool get isCacheValid {
|
|
if (_lastFetchTime == null) return false;
|
|
return DateTime.now().difference(_lastFetchTime!) < _cacheValidDuration;
|
|
}
|
|
|
|
// 장비 타입 목록
|
|
List<LookupItem> get equipmentTypes => _lookupData?.equipmentTypes ?? [];
|
|
|
|
// 장비 상태 목록
|
|
List<LookupItem> get equipmentStatuses => _lookupData?.equipmentStatuses ?? [];
|
|
|
|
// 라이선스 타입 목록
|
|
List<LookupItem> get licenseTypes => _lookupData?.licenseTypes ?? [];
|
|
|
|
// 제조사 목록
|
|
List<LookupItem> get manufacturers => _lookupData?.manufacturers ?? [];
|
|
|
|
// 사용자 역할 목록
|
|
List<LookupItem> get userRoles => _lookupData?.userRoles ?? [];
|
|
|
|
// 회사 상태 목록
|
|
List<LookupItem> get companyStatuses => _lookupData?.companyStatuses ?? [];
|
|
|
|
// 창고 타입 목록
|
|
List<LookupItem> get warehouseTypes => _lookupData?.warehouseTypes ?? [];
|
|
|
|
// 전체 조회 데이터 로드
|
|
Future<void> loadAllLookups({bool forceRefresh = false}) async {
|
|
// 캐시가 유효하고 강제 새로고침이 아니면 캐시 사용
|
|
if (!forceRefresh && isCacheValid && hasData) {
|
|
return;
|
|
}
|
|
|
|
_isLoading = true;
|
|
_error = null;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final result = await _dataSource.getAllLookups();
|
|
|
|
result.fold(
|
|
(failure) {
|
|
_error = failure.message;
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
},
|
|
(data) {
|
|
_lookupData = data;
|
|
_lastFetchTime = DateTime.now();
|
|
_error = null;
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
},
|
|
);
|
|
} catch (e) {
|
|
_error = '조회 데이터 로드 중 오류가 발생했습니다: $e';
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
// 특정 타입의 조회 데이터만 로드
|
|
Future<Map<String, List<LookupItem>>?> loadLookupsByType(String type) async {
|
|
try {
|
|
final result = await _dataSource.getLookupsByType(type);
|
|
|
|
return result.fold(
|
|
(failure) {
|
|
_error = failure.message;
|
|
notifyListeners();
|
|
return null;
|
|
},
|
|
(data) {
|
|
// 부분 업데이트 (필요한 경우)
|
|
_updatePartialData(type, data);
|
|
return data;
|
|
},
|
|
);
|
|
} catch (e) {
|
|
_error = '타입별 조회 데이터 로드 중 오류가 발생했습니다: $e';
|
|
notifyListeners();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// 부분 데이터 업데이트
|
|
void _updatePartialData(String type, Map<String, List<LookupItem>> data) {
|
|
if (_lookupData == null) {
|
|
// 전체 데이터가 없으면 부분 데이터만으로 초기화
|
|
_lookupData = LookupData(
|
|
equipmentTypes: data['equipment_types'] ?? [],
|
|
equipmentStatuses: data['equipment_statuses'] ?? [],
|
|
licenseTypes: data['license_types'] ?? [],
|
|
manufacturers: data['manufacturers'] ?? [],
|
|
userRoles: data['user_roles'] ?? [],
|
|
companyStatuses: data['company_statuses'] ?? [],
|
|
warehouseTypes: data['warehouse_types'] ?? [],
|
|
);
|
|
} else {
|
|
// 기존 데이터의 특정 부분만 업데이트
|
|
_lookupData = _lookupData!.copyWith(
|
|
equipmentTypes: data['equipment_types'] ?? _lookupData!.equipmentTypes,
|
|
equipmentStatuses: data['equipment_statuses'] ?? _lookupData!.equipmentStatuses,
|
|
licenseTypes: data['license_types'] ?? _lookupData!.licenseTypes,
|
|
manufacturers: data['manufacturers'] ?? _lookupData!.manufacturers,
|
|
userRoles: data['user_roles'] ?? _lookupData!.userRoles,
|
|
companyStatuses: data['company_statuses'] ?? _lookupData!.companyStatuses,
|
|
warehouseTypes: data['warehouse_types'] ?? _lookupData!.warehouseTypes,
|
|
);
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
// 코드로 아이템 찾기
|
|
LookupItem? findByCode(List<LookupItem> items, String code) {
|
|
try {
|
|
return items.firstWhere((item) => item.code == code);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// 이름으로 아이템 찾기
|
|
LookupItem? findByName(List<LookupItem> items, String name) {
|
|
try {
|
|
return items.firstWhere((item) => item.name == name);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// 캐시 클리어
|
|
void clearCache() {
|
|
_lookupData = null;
|
|
_lastFetchTime = null;
|
|
_error = null;
|
|
notifyListeners();
|
|
}
|
|
} |