refactor: UI 화면 통합 및 불필요한 파일 정리
Some checks failed
Flutter Test & Quality Check / Build APK (push) Has been cancelled
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled

- 모든 *_redesign.dart 파일을 기본 화면 파일로 통합
- 백업용 컨트롤러 파일들 제거 (*_controller.backup.dart)
- 사용하지 않는 예제 및 테스트 파일 제거
- Clean Architecture 적용 후 남은 정리 작업 완료
- 테스트 코드 정리 및 구조 개선 준비

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-08-11 14:00:44 +09:00
parent 162fe08618
commit 1e6da44917
103 changed files with 1224 additions and 2976 deletions

View File

@@ -1,467 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:superport/core/errors/failures.dart';
import 'package:superport/models/license_model.dart';
import 'package:superport/services/license_service.dart';
// 라이센스 상태 필터
enum LicenseStatusFilter {
all,
active,
inactive,
expiringSoon, // 30일 이내
expired,
}
// 라이센스 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
class LicenseListController extends ChangeNotifier {
final LicenseService _licenseService = GetIt.instance<LicenseService>();
List<License> _licenses = [];
List<License> _filteredLicenses = [];
bool _isLoading = false;
String? _error;
String _searchQuery = '';
int _currentPage = 1;
final int _pageSize = 20;
bool _hasMore = true;
int _total = 0;
// 필터 옵션
int? _selectedCompanyId;
bool? _isActive;
String? _licenseType;
LicenseStatusFilter _statusFilter = LicenseStatusFilter.all;
String _sortBy = 'expiry_date';
String _sortOrder = 'asc';
// 선택된 라이선스 관리
final Set<int> _selectedLicenseIds = {};
// 통계 데이터
Map<String, int> _statistics = {
'total': 0,
'active': 0,
'inactive': 0,
'expiringSoon': 0,
'expired': 0,
};
// 검색 디바운스를 위한 타이머
Timer? _debounceTimer;
LicenseListController();
// Getters
List<License> get licenses => _filteredLicenses;
bool get isLoading => _isLoading;
String? get error => _error;
String get searchQuery => _searchQuery;
int get currentPage => _currentPage;
bool get hasMore => _hasMore;
int get total => _total;
int? get selectedCompanyId => _selectedCompanyId;
bool? get isActive => _isActive;
String? get licenseType => _licenseType;
LicenseStatusFilter get statusFilter => _statusFilter;
Set<int> get selectedLicenseIds => _selectedLicenseIds;
Map<String, int> get statistics => _statistics;
// 선택된 라이선스 개수
int get selectedCount => _selectedLicenseIds.length;
// 전체 선택 여부 확인
bool get isAllSelected =>
_filteredLicenses.isNotEmpty &&
_filteredLicenses.where((l) => l.id != null)
.every((l) => _selectedLicenseIds.contains(l.id));
// 데이터 로드
Future<void> loadData({bool isInitialLoad = true}) async {
if (_isLoading) return;
_isLoading = true;
_error = null;
notifyListeners();
try {
// API 사용 - 전체 데이터 로드
print('╔══════════════════════════════════════════════════════════');
print('║ 🔧 유지보수 목록 API 호출 시작');
print('║ • 회사 필터: ${_selectedCompanyId ?? "전체"}');
print('║ • 활성 필터: ${_isActive != null ? (_isActive! ? "활성" : "비활성") : "전체"}');
print('║ • 라이센스 타입: ${_licenseType ?? "전체"}');
print('╚══════════════════════════════════════════════════════════');
// 전체 데이터를 가져오기 위해 큰 perPage 값 사용
final fetchedLicenses = await _licenseService.getLicenses(
page: 1,
perPage: 1000, // 충분히 큰 값으로 전체 데이터 로드
isActive: _isActive,
companyId: _selectedCompanyId,
licenseType: _licenseType,
);
print('╔══════════════════════════════════════════════════════════');
print('║ 📊 유지보수 목록 로드 완료');
print('║ ▶ 총 라이센스 수: ${fetchedLicenses.length}');
print('╟──────────────────────────────────────────────────────────');
// 상태별 통계
int activeCount = 0;
int expiringSoonCount = 0;
int expiredCount = 0;
final now = DateTime.now();
for (final license in fetchedLicenses) {
if (license.expiryDate != null) {
final daysUntil = license.expiryDate!.difference(now).inDays;
if (daysUntil < 0) {
expiredCount++;
} else if (daysUntil <= 30) {
expiringSoonCount++;
} else {
activeCount++;
}
} else {
activeCount++;
}
}
print('║ • 활성: $activeCount개');
print('║ • 만료 임박 (30일 이내): $expiringSoonCount개');
print('║ • 만료됨: $expiredCount개');
print('╟──────────────────────────────────────────────────────────');
print('║ 📑 전체 데이터 로드 완료');
print('║ • View에서 페이지네이션 처리 예정');
print('╚══════════════════════════════════════════════════════════');
_licenses = fetchedLicenses;
_hasMore = false; // 전체 데이터를 로드했으므로 더 이상 로드할 필요 없음
_total = fetchedLicenses.length;
debugPrint('📑 _applySearchFilter 호출 전: _licenses=${_licenses.length}');
_applySearchFilter();
_applyStatusFilter();
await _updateStatistics();
debugPrint('📑 _applySearchFilter 호출 후: _filteredLicenses=${_filteredLicenses.length}');
} catch (e) {
debugPrint('❌ loadData 에러 발생: $e');
_error = e.toString();
} finally {
_isLoading = false;
debugPrint('📑 loadData 종료: _filteredLicenses=${_filteredLicenses.length}');
notifyListeners();
}
}
// 다음 페이지 로드
Future<void> loadNextPage() async {
if (!_hasMore || _isLoading) return;
_currentPage++;
await loadData(isInitialLoad: false);
}
// 검색 (디바운싱 적용)
void search(String query) {
_searchQuery = query;
// 기존 타이머 취소
_debounceTimer?.cancel();
// API 검색은 디바운싱 적용 (300ms)
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
loadData();
});
}
// 검색 필터 적용
void _applySearchFilter() {
debugPrint('🔎 _applySearchFilter 시작: _searchQuery="$_searchQuery", _licenses=${_licenses.length}');
if (_searchQuery.isEmpty) {
_filteredLicenses = List.from(_licenses);
debugPrint('🔎 검색어 없음: 전체 복사 ${_filteredLicenses.length}');
} else {
_filteredLicenses = _licenses.where((license) {
final productName = license.productName?.toLowerCase() ?? '';
final licenseKey = license.licenseKey.toLowerCase();
final vendor = license.vendor?.toLowerCase() ?? '';
final companyName = license.companyName?.toLowerCase() ?? '';
final searchLower = _searchQuery.toLowerCase();
return productName.contains(searchLower) ||
licenseKey.contains(searchLower) ||
vendor.contains(searchLower) ||
companyName.contains(searchLower);
}).toList();
debugPrint('🔎 검색 필터링 완료: ${_filteredLicenses.length}');
}
}
// 상태 필터 적용
void _applyStatusFilter() {
if (_statusFilter == LicenseStatusFilter.all) return;
final now = DateTime.now();
_filteredLicenses = _filteredLicenses.where((license) {
switch (_statusFilter) {
case LicenseStatusFilter.active:
return license.isActive;
case LicenseStatusFilter.inactive:
return !license.isActive;
case LicenseStatusFilter.expiringSoon:
if (license.expiryDate != null) {
final days = license.expiryDate!.difference(now).inDays;
return days > 0 && days <= 30;
}
return false;
case LicenseStatusFilter.expired:
if (license.expiryDate != null) {
return license.expiryDate!.isBefore(now);
}
return false;
case LicenseStatusFilter.all:
default:
return true;
}
}).toList();
}
// 필터 설정
void setFilters({
int? companyId,
bool? isActive,
String? licenseType,
}) {
_selectedCompanyId = companyId;
_isActive = isActive;
_licenseType = licenseType;
loadData();
}
// 필터 초기화
void clearFilters() {
_selectedCompanyId = null;
_isActive = null;
_licenseType = null;
_searchQuery = '';
loadData();
}
// 라이센스 삭제
Future<void> deleteLicense(int id) async {
try {
await _licenseService.deleteLicense(id);
// 목록에서 제거
_licenses.removeWhere((l) => l.id == id);
_applySearchFilter();
_total--;
notifyListeners();
} catch (e) {
_error = e.toString();
notifyListeners();
}
}
// 새로고침
Future<void> refresh() async {
await loadData();
}
// 만료 예정 라이선스 조회
Future<List<License>> getExpiringLicenses({int days = 30}) async {
try {
return await _licenseService.getExpiringLicenses(days: days);
} catch (e) {
_error = e.toString();
notifyListeners();
return [];
}
}
// 상태별 라이선스 개수 조회
Future<Map<String, int>> getLicenseStatusCounts() async {
try {
// API에서 상태별 개수 조회 (실제로는 별도 엔드포인트가 있다면 사용)
final activeCount = await _licenseService.getTotalLicenses(isActive: true);
final inactiveCount = await _licenseService.getTotalLicenses(isActive: false);
final expiringLicenses = await getExpiringLicenses(days: 30);
return {
'active': activeCount,
'inactive': inactiveCount,
'expiring': expiringLicenses.length,
'total': activeCount + inactiveCount,
};
} catch (e) {
return {'active': 0, 'inactive': 0, 'expiring': 0, 'total': 0};
}
}
// 정렬 변경
void sortBy(String field, String order) {
_sortBy = field;
_sortOrder = order;
loadData();
}
// 상태 필터 변경
Future<void> changeStatusFilter(LicenseStatusFilter filter) async {
_statusFilter = filter;
await loadData();
}
// 라이선스 선택/해제
void selectLicense(int? id, bool? isSelected) {
if (id == null) return;
if (isSelected == true) {
_selectedLicenseIds.add(id);
} else {
_selectedLicenseIds.remove(id);
}
notifyListeners();
}
// 전체 선택/해제
void selectAll(bool? isSelected) {
if (isSelected == true) {
// 현재 필터링된 라이선스 모두 선택
for (var license in _filteredLicenses) {
if (license.id != null) {
_selectedLicenseIds.add(license.id!);
}
}
} else {
// 모두 해제
_selectedLicenseIds.clear();
}
notifyListeners();
}
// 선택된 라이선스 목록 반환
List<License> getSelectedLicenses() {
return _filteredLicenses
.where((l) => l.id != null && _selectedLicenseIds.contains(l.id))
.toList();
}
// 선택 초기화
void clearSelection() {
_selectedLicenseIds.clear();
notifyListeners();
}
// 라이선스 할당
Future<bool> assignLicense(int licenseId, int userId) async {
try {
await _licenseService.assignLicense(licenseId, userId);
await loadData();
clearSelection();
return true;
} catch (e) {
_error = e.toString();
notifyListeners();
return false;
}
}
// 라이선스 할당 해제
Future<bool> unassignLicense(int licenseId) async {
try {
await _licenseService.unassignLicense(licenseId);
await loadData();
clearSelection();
return true;
} catch (e) {
_error = e.toString();
notifyListeners();
return false;
}
}
// 선택된 라이선스 일괄 삭제
Future<void> deleteSelectedLicenses() async {
if (_selectedLicenseIds.isEmpty) return;
final selectedIds = List<int>.from(_selectedLicenseIds);
int successCount = 0;
int failCount = 0;
for (var id in selectedIds) {
try {
await deleteLicense(id);
successCount++;
} catch (e) {
failCount++;
debugPrint('라이선스 $id 삭제 실패: $e');
}
}
_selectedLicenseIds.clear();
await loadData();
if (successCount > 0) {
debugPrint('$successCount개 라이선스 삭제 완료');
}
if (failCount > 0) {
debugPrint('$failCount개 라이선스 삭제 실패');
}
}
// 통계 업데이트
Future<void> _updateStatistics() async {
try {
final counts = await getLicenseStatusCounts();
final now = DateTime.now();
int expiringSoonCount = 0;
int expiredCount = 0;
for (var license in _licenses) {
if (license.expiryDate != null) {
final days = license.expiryDate!.difference(now).inDays;
if (days <= 0) {
expiredCount++;
} else if (days <= 30) {
expiringSoonCount++;
}
}
}
_statistics = {
'total': counts['total'] ?? 0,
'active': counts['active'] ?? 0,
'inactive': counts['inactive'] ?? 0,
'expiringSoon': expiringSoonCount,
'expired': expiredCount,
};
} catch (e) {
debugPrint('❌ 통계 업데이트 오류: $e');
// 오류 발생 시 기본값 사용
_statistics = {
'total': _licenses.length,
'active': 0,
'inactive': 0,
'expiringSoon': 0,
'expired': 0,
};
}
}
// 만료일까지 남은 일수 계산
int? getDaysUntilExpiry(License license) {
if (license.expiryDate == null) return null;
return license.expiryDate!.difference(DateTime.now()).inDays;
}
@override
void dispose() {
_debounceTimer?.cancel();
super.dispose();
}
}

View File

@@ -74,8 +74,8 @@ class LicenseListController extends BaseListController<License> {
required PaginationParams params,
Map<String, dynamic>? additionalFilters,
}) async {
// API 호출
final fetchedLicenses = await ErrorHandler.handleApiCall(
// API 호출 (PaginatedResponse 반환)
final response = await ErrorHandler.handleApiCall(
() => _licenseService.getLicenses(
page: params.page,
perPage: params.perPage,
@@ -88,7 +88,7 @@ class LicenseListController extends BaseListController<License> {
},
);
if (fetchedLicenses == null) {
if (response == null) {
return PagedResult(
items: [],
meta: PaginationMeta(
@@ -103,21 +103,19 @@ class LicenseListController extends BaseListController<License> {
}
// 통계 업데이트
await _updateStatistics(fetchedLicenses);
await _updateStatistics(response.items);
// 임시로 메타데이터 생성 (추후 API에서 실제 메타데이터 반환 시 수정)
// PaginatedResponse를 PagedResult로 변환
final meta = PaginationMeta(
currentPage: params.page,
perPage: params.perPage,
total: fetchedLicenses.length < params.perPage ?
(params.page - 1) * params.perPage + fetchedLicenses.length :
params.page * params.perPage + 1,
totalPages: fetchedLicenses.length < params.perPage ? params.page : params.page + 1,
hasNext: fetchedLicenses.length >= params.perPage,
hasPrevious: params.page > 1,
currentPage: response.page,
perPage: response.size,
total: response.totalElements,
totalPages: response.totalPages,
hasNext: !response.last,
hasPrevious: !response.first,
);
return PagedResult(items: fetchedLicenses, meta: meta);
return PagedResult(items: response.items, meta: meta);
}
@override