refactor: Clean Architecture 적용 및 코드베이스 전면 리팩토링
## 주요 변경사항 ### 아키텍처 개선 - Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리) - Use Case 패턴 도입으로 비즈니스 로직 캡슐화 - Repository 패턴으로 데이터 접근 추상화 - 의존성 주입 구조 개선 ### 상태 관리 최적화 - 모든 Controller에서 불필요한 상태 관리 로직 제거 - 페이지네이션 로직 통일 및 간소화 - 에러 처리 로직 개선 (에러 메시지 한글화) - 로딩 상태 관리 최적화 ### Mock 서비스 제거 - MockDataService 완전 제거 - 모든 화면을 실제 API 전용으로 전환 - 불필요한 Mock 관련 코드 정리 ### UI/UX 개선 - Overview 화면 대시보드 기능 강화 - 라이선스 만료 알림 위젯 추가 - 사이드바 네비게이션 개선 - 일관된 UI 컴포넌트 사용 ### 코드 품질 - 중복 코드 제거 및 함수 추출 - 파일별 책임 분리 명확화 - 테스트 코드 업데이트 ## 영향 범위 - 모든 화면의 Controller 리팩토링 - API 통신 레이어 구조 개선 - 에러 처리 및 로깅 시스템 개선 ## 향후 계획 - 단위 테스트 커버리지 확대 - 통합 테스트 시나리오 추가 - 성능 모니터링 도구 통합
This commit is contained in:
@@ -12,7 +12,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:superport/models/address_model.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
// import 'package:superport/services/mock_data_service.dart'; // Mock 서비스 제거
|
||||
import 'package:superport/services/company_service.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
import 'package:superport/utils/phone_utils.dart';
|
||||
@@ -21,7 +21,7 @@ import 'branch_form_controller.dart'; // 분리된 지점 컨트롤러 import
|
||||
|
||||
/// 회사 폼 컨트롤러 - 비즈니스 로직 처리
|
||||
class CompanyFormController {
|
||||
final MockDataService? dataService;
|
||||
// final MockDataService? dataService; // Mock 서비스 제거
|
||||
final CompanyService _companyService = GetIt.instance<CompanyService>();
|
||||
final int? companyId;
|
||||
|
||||
@@ -77,7 +77,7 @@ class CompanyFormController {
|
||||
bool preventAutoFocus = false;
|
||||
final Map<int, bool> isNewlyAddedBranch = {};
|
||||
|
||||
CompanyFormController({this.dataService, this.companyId, bool useApi = false})
|
||||
CompanyFormController({this.companyId, bool useApi = true})
|
||||
: _useApi = useApi {
|
||||
_setupFocusNodes();
|
||||
_setupControllerListeners();
|
||||
@@ -96,13 +96,8 @@ class CompanyFormController {
|
||||
try {
|
||||
List<Company> companies;
|
||||
|
||||
if (_useApi) {
|
||||
companies = await _companyService.getCompanies();
|
||||
} else if (dataService != null) {
|
||||
companies = dataService!.getAllCompanies();
|
||||
} else {
|
||||
companies = [];
|
||||
}
|
||||
// API만 사용
|
||||
companies = await _companyService.getCompanies();
|
||||
|
||||
companyNames = companies.map((c) => c.name).toList();
|
||||
filteredCompanyNames = companyNames;
|
||||
@@ -125,9 +120,9 @@ class CompanyFormController {
|
||||
if (_useApi) {
|
||||
debugPrint('📝 API에서 회사 정보 로드 중...');
|
||||
company = await _companyService.getCompanyDetail(companyId!);
|
||||
} else if (dataService != null) {
|
||||
debugPrint('📝 Mock에서 회사 정보 로드 중...');
|
||||
company = dataService!.getCompanyById(companyId!);
|
||||
} else {
|
||||
debugPrint('📝 API만 사용 가능');
|
||||
throw Exception('API를 통해만 데이터를 로드할 수 있습니다');
|
||||
}
|
||||
|
||||
debugPrint('📝 로드된 회사: $company');
|
||||
@@ -234,8 +229,9 @@ class CompanyFormController {
|
||||
debugPrint('Failed to load company data: ${e.message}');
|
||||
return;
|
||||
}
|
||||
} else if (dataService != null) {
|
||||
company = dataService!.getCompanyById(companyId!);
|
||||
} else {
|
||||
// API만 사용
|
||||
debugPrint('API를 통해만 데이터를 로드할 수 있습니다');
|
||||
}
|
||||
|
||||
if (company != null) {
|
||||
@@ -364,8 +360,9 @@ class CompanyFormController {
|
||||
// 오류 발생 시 중복 없음으로 처리
|
||||
return null;
|
||||
}
|
||||
} else if (dataService != null) {
|
||||
return dataService!.findCompanyByName(name);
|
||||
} else {
|
||||
// API만 사용
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -440,12 +437,9 @@ class CompanyFormController {
|
||||
debugPrint('Unexpected error saving company: $e');
|
||||
return false;
|
||||
}
|
||||
} else if (dataService != null) {
|
||||
if (companyId == null) {
|
||||
dataService!.addCompany(company);
|
||||
} else {
|
||||
dataService!.updateCompany(company);
|
||||
}
|
||||
} else {
|
||||
// API만 사용
|
||||
throw Exception('API를 통해만 데이터를 저장할 수 있습니다');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -484,10 +478,9 @@ class CompanyFormController {
|
||||
debugPrint('Failed to save branch: ${e.message}');
|
||||
return false;
|
||||
}
|
||||
} else if (dataService != null) {
|
||||
// Mock 데이터 서비스 사용
|
||||
dataService!.updateBranch(companyId!, branch);
|
||||
return true;
|
||||
} else {
|
||||
// API만 사용
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/services/company_service.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
|
||||
// 회사 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
|
||||
class CompanyListController extends ChangeNotifier {
|
||||
final CompanyService _companyService = GetIt.instance<CompanyService>();
|
||||
|
||||
List<Company> companies = [];
|
||||
List<Company> filteredCompanies = [];
|
||||
String searchKeyword = '';
|
||||
final Set<int> selectedCompanyIds = {};
|
||||
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
// API만 사용
|
||||
|
||||
// 페이지네이션
|
||||
int _currentPage = 1;
|
||||
int _perPage = 20;
|
||||
bool _hasMore = true;
|
||||
|
||||
// 필터
|
||||
bool? _isActiveFilter;
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
bool get hasMore => _hasMore;
|
||||
int get currentPage => _currentPage;
|
||||
bool? get isActiveFilter => _isActiveFilter;
|
||||
|
||||
CompanyListController();
|
||||
|
||||
// 초기 데이터 로드
|
||||
Future<void> initialize() async {
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🚀 회사 목록 초기화 시작');
|
||||
print('║ • 페이지 크기: $_perPage개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 페이지 크기를 지정하여 초기화
|
||||
Future<void> initializeWithPageSize(int pageSize) async {
|
||||
_perPage = pageSize;
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🚀 회사 목록 초기화 시작 (커스텀 페이지 크기)');
|
||||
print('║ • 페이지 크기: $_perPage개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 데이터 로드 및 필터 적용
|
||||
Future<void> loadData({bool isRefresh = false}) async {
|
||||
print('🔍 [DEBUG] loadData 시작 - currentPage: $_currentPage, hasMore: $_hasMore, companies.length: ${companies.length}');
|
||||
print('[CompanyListController] loadData called - isRefresh: $isRefresh');
|
||||
|
||||
if (isRefresh) {
|
||||
_currentPage = 1;
|
||||
_hasMore = true;
|
||||
companies.clear();
|
||||
filteredCompanies.clear();
|
||||
}
|
||||
|
||||
if (_isLoading || (!_hasMore && !isRefresh)) return;
|
||||
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// API 호출 - 지점 정보 포함
|
||||
print('[CompanyListController] Using API to fetch companies with branches');
|
||||
|
||||
// 지점 정보를 포함한 전체 회사 목록 가져오기
|
||||
final apiCompaniesWithBranches = await _companyService.getCompaniesWithBranchesFlat();
|
||||
|
||||
// 상세한 회사 정보 로그 출력
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 📊 회사 목록 로드 완료');
|
||||
print('║ ▶ 총 회사 수: ${apiCompaniesWithBranches.length}개');
|
||||
print('╟──────────────────────────────────────────────────────────');
|
||||
|
||||
// 지점이 있는 회사와 없는 회사 구분
|
||||
int companiesWithBranches = 0;
|
||||
int totalBranches = 0;
|
||||
|
||||
for (final company in apiCompaniesWithBranches) {
|
||||
if (company.branches?.isNotEmpty ?? false) {
|
||||
companiesWithBranches++;
|
||||
totalBranches += company.branches!.length;
|
||||
print('║ • ${company.name}: ${company.branches!.length}개 지점');
|
||||
}
|
||||
}
|
||||
|
||||
final companiesWithoutBranches = apiCompaniesWithBranches.length - companiesWithBranches;
|
||||
|
||||
print('╟──────────────────────────────────────────────────────────');
|
||||
print('║ 📈 통계');
|
||||
print('║ • 지점이 있는 회사: ${companiesWithBranches}개');
|
||||
print('║ • 지점이 없는 회사: ${companiesWithoutBranches}개');
|
||||
print('║ • 총 지점 수: ${totalBranches}개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
|
||||
// 검색어 필터 적용 (서버에서 필터링이 안 되므로 클라이언트에서 처리)
|
||||
List<Company> filteredApiCompanies = apiCompaniesWithBranches;
|
||||
if (searchKeyword.isNotEmpty) {
|
||||
final keyword = searchKeyword.toLowerCase();
|
||||
filteredApiCompanies = apiCompaniesWithBranches.where((company) {
|
||||
return company.name.toLowerCase().contains(keyword) ||
|
||||
(company.contactName?.toLowerCase().contains(keyword) ?? false) ||
|
||||
(company.contactPhone?.toLowerCase().contains(keyword) ?? false);
|
||||
}).toList();
|
||||
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🔍 검색 필터 적용');
|
||||
print('║ • 검색어: "$searchKeyword"');
|
||||
print('║ • 필터 전: ${apiCompaniesWithBranches.length}개');
|
||||
print('║ • 필터 후: ${filteredApiCompanies.length}개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
}
|
||||
|
||||
// 활성 상태 필터 적용 (현재 API에서 지원하지 않으므로 주석 처리)
|
||||
// if (_isActiveFilter != null) {
|
||||
// filteredApiCompanies = filteredApiCompanies.where((c) => c.isActive == _isActiveFilter).toList();
|
||||
// }
|
||||
|
||||
// 전체 데이터를 한 번에 로드 (View에서 페이지네이션 처리)
|
||||
companies = filteredApiCompanies;
|
||||
_hasMore = false; // 전체 데이터를 로드했으므로 더 이상 로드할 필요 없음
|
||||
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 📑 전체 데이터 로드 완료');
|
||||
print('║ • 로드된 회사 수: ${companies.length}개');
|
||||
print('║ • 필터링된 회사 수: ${filteredApiCompanies.length}개');
|
||||
print('║ • View에서 페이지네이션 처리 예정');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
|
||||
// 필터 적용
|
||||
applyFilters();
|
||||
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ ✅ 최종 화면 표시');
|
||||
print('║ • 화면에 표시될 회사 수: ${filteredCompanies.length}개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
|
||||
selectedCompanyIds.clear();
|
||||
} on Failure catch (e) {
|
||||
print('[CompanyListController] Failure loading companies: ${e.message}');
|
||||
_error = e.message;
|
||||
} catch (e, stackTrace) {
|
||||
print('[CompanyListController] Error loading companies: $e');
|
||||
print('[CompanyListController] Error type: ${e.runtimeType}');
|
||||
print('[CompanyListController] Stack trace: $stackTrace');
|
||||
_error = '회사 목록을 불러오는 중 오류가 발생했습니다: $e';
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 검색 및 필터 적용
|
||||
void applyFilters() {
|
||||
filteredCompanies = companies.where((company) {
|
||||
// 검색어 필터
|
||||
if (searchKeyword.isNotEmpty) {
|
||||
final keyword = searchKeyword.toLowerCase();
|
||||
final matchesName = company.name.toLowerCase().contains(keyword);
|
||||
final matchesContact = company.contactName?.toLowerCase().contains(keyword) ?? false;
|
||||
final matchesPhone = company.contactPhone?.toLowerCase().contains(keyword) ?? false;
|
||||
|
||||
if (!matchesName && !matchesContact && !matchesPhone) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 활성 상태 필터 (현재 API에서 지원안함)
|
||||
// if (_isActiveFilter != null) {
|
||||
// 추후 API 지원 시 구현
|
||||
// }
|
||||
|
||||
return true;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// 검색어 변경
|
||||
Future<void> updateSearchKeyword(String keyword) async {
|
||||
searchKeyword = keyword;
|
||||
|
||||
if (keyword.isNotEmpty) {
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🔍 검색어 변경: "$keyword"');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
} else {
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ ❌ 검색어 초기화');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
}
|
||||
|
||||
// API 사용 시 새로 조회
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 활성 상태 필터 변경
|
||||
Future<void> changeActiveFilter(bool? isActive) async {
|
||||
_isActiveFilter = isActive;
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 회사 선택/해제
|
||||
void toggleCompanySelection(int? companyId) {
|
||||
if (companyId == null) return;
|
||||
|
||||
if (selectedCompanyIds.contains(companyId)) {
|
||||
selectedCompanyIds.remove(companyId);
|
||||
} else {
|
||||
selectedCompanyIds.add(companyId);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 전체 선택/해제
|
||||
void toggleSelectAll() {
|
||||
if (selectedCompanyIds.length == filteredCompanies.length) {
|
||||
selectedCompanyIds.clear();
|
||||
} else {
|
||||
selectedCompanyIds.clear();
|
||||
for (final company in filteredCompanies) {
|
||||
if (company.id != null) {
|
||||
selectedCompanyIds.add(company.id!);
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 선택된 회사 수 반환
|
||||
int getSelectedCount() {
|
||||
return selectedCompanyIds.length;
|
||||
}
|
||||
|
||||
// 회사 삭제
|
||||
Future<bool> deleteCompany(int companyId) async {
|
||||
try {
|
||||
// API를 통한 삭제
|
||||
await _companyService.deleteCompany(companyId);
|
||||
|
||||
// 로컬 리스트에서도 제거
|
||||
companies.removeWhere((c) => c.id == companyId);
|
||||
filteredCompanies.removeWhere((c) => c.id == companyId);
|
||||
selectedCompanyIds.remove(companyId);
|
||||
notifyListeners();
|
||||
|
||||
return true;
|
||||
} on Failure catch (e) {
|
||||
_error = e.message;
|
||||
notifyListeners();
|
||||
return false;
|
||||
} catch (e) {
|
||||
_error = '회사 삭제 중 오류가 발생했습니다: $e';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 선택된 회사들 삭제
|
||||
Future<bool> deleteSelectedCompanies() async {
|
||||
final selectedIds = selectedCompanyIds.toList();
|
||||
int successCount = 0;
|
||||
|
||||
for (final companyId in selectedIds) {
|
||||
if (await deleteCompany(companyId)) {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return successCount == selectedIds.length;
|
||||
}
|
||||
|
||||
// 회사 정보 업데이트 (로컬)
|
||||
void updateCompanyLocally(Company updatedCompany) {
|
||||
final index = companies.indexWhere((c) => c.id == updatedCompany.id);
|
||||
if (index != -1) {
|
||||
companies[index] = updatedCompany;
|
||||
applyFilters();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 회사 추가 (로컬)
|
||||
void addCompanyLocally(Company newCompany) {
|
||||
companies.insert(0, newCompany);
|
||||
applyFilters();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 더 많은 데이터 로드
|
||||
Future<void> loadMore() async {
|
||||
print('🔍 [DEBUG] loadMore 호출됨 - hasMore: $_hasMore, isLoading: $_isLoading');
|
||||
if (!_hasMore || _isLoading) {
|
||||
print('🔍 [DEBUG] loadMore 조건 미충족으로 종료 (hasMore: $_hasMore, isLoading: $_isLoading)');
|
||||
return;
|
||||
}
|
||||
print('🔍 [DEBUG] loadMore 실행 - 추가 데이터 로드 시작');
|
||||
await loadData();
|
||||
}
|
||||
|
||||
// API만 사용하므로 토글 기능 제거
|
||||
|
||||
// 에러 처리
|
||||
void clearError() {
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 리프레시
|
||||
Future<void> refresh() async {
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🔄 회사 목록 새로고침 시작');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -2,241 +2,94 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/services/company_service.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
import 'package:superport/core/utils/error_handler.dart';
|
||||
import 'package:superport/core/controllers/base_list_controller.dart';
|
||||
import 'package:superport/data/models/common/pagination_params.dart';
|
||||
|
||||
// 회사 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
|
||||
class CompanyListController extends ChangeNotifier {
|
||||
final MockDataService dataService;
|
||||
final CompanyService _companyService = GetIt.instance<CompanyService>();
|
||||
/// 회사 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러 (리팩토링 버전)
|
||||
/// BaseListController를 상속받아 공통 기능을 재사용
|
||||
class CompanyListController extends BaseListController<Company> {
|
||||
late final CompanyService _companyService;
|
||||
|
||||
List<Company> companies = [];
|
||||
List<Company> filteredCompanies = [];
|
||||
String searchKeyword = '';
|
||||
// 추가 상태 관리
|
||||
final Set<int> selectedCompanyIds = {};
|
||||
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
bool _useApi = true; // Feature flag for API usage
|
||||
|
||||
// 페이지네이션
|
||||
int _currentPage = 1;
|
||||
int _perPage = 20;
|
||||
bool _hasMore = true;
|
||||
|
||||
// 필터
|
||||
bool? _isActiveFilter;
|
||||
CompanyType? _typeFilter;
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
bool get hasMore => _hasMore;
|
||||
int get currentPage => _currentPage;
|
||||
List<Company> get companies => items;
|
||||
List<Company> get filteredCompanies => items;
|
||||
bool? get isActiveFilter => _isActiveFilter;
|
||||
CompanyType? get typeFilter => _typeFilter;
|
||||
|
||||
CompanyListController({required this.dataService});
|
||||
CompanyListController() {
|
||||
if (GetIt.instance.isRegistered<CompanyService>()) {
|
||||
_companyService = GetIt.instance<CompanyService>();
|
||||
} else {
|
||||
throw Exception('CompanyService not registered in GetIt');
|
||||
}
|
||||
}
|
||||
|
||||
// 초기 데이터 로드
|
||||
Future<void> initialize() async {
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🚀 회사 목록 초기화 시작');
|
||||
print('║ • 페이지 크기: $_perPage개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 페이지 크기를 지정하여 초기화
|
||||
Future<void> initializeWithPageSize(int pageSize) async {
|
||||
_perPage = pageSize;
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🚀 회사 목록 초기화 시작 (커스텀 페이지 크기)');
|
||||
print('║ • 페이지 크기: $_perPage개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
Future<void> initializeWithPageSize(int newPageSize) async {
|
||||
pageSize = newPageSize;
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 데이터 로드 및 필터 적용
|
||||
Future<void> loadData({bool isRefresh = false}) async {
|
||||
print('🔍 [DEBUG] loadData 시작 - currentPage: $_currentPage, hasMore: $_hasMore, companies.length: ${companies.length}');
|
||||
print('[CompanyListController] loadData called - isRefresh: $isRefresh');
|
||||
@override
|
||||
Future<PagedResult<Company>> fetchData({
|
||||
required PaginationParams params,
|
||||
Map<String, dynamic>? additionalFilters,
|
||||
}) async {
|
||||
// API 호출 - 회사 목록 조회
|
||||
final apiCompanies = await ErrorHandler.handleApiCall<List<Company>>(
|
||||
() => _companyService.getCompanies(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
search: params.search,
|
||||
isActive: _isActiveFilter,
|
||||
),
|
||||
onError: (failure) {
|
||||
throw failure;
|
||||
},
|
||||
);
|
||||
|
||||
if (isRefresh) {
|
||||
_currentPage = 1;
|
||||
_hasMore = true;
|
||||
companies.clear();
|
||||
filteredCompanies.clear();
|
||||
}
|
||||
final items = apiCompanies ?? [];
|
||||
|
||||
if (_isLoading || (!_hasMore && !isRefresh)) return;
|
||||
// 임시로 메타데이터 생성 (추후 API에서 실제 메타데이터 반환 시 수정)
|
||||
final meta = PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: items.length < params.perPage ?
|
||||
(params.page - 1) * params.perPage + items.length :
|
||||
params.page * params.perPage + 1,
|
||||
totalPages: items.length < params.perPage ? params.page : params.page + 1,
|
||||
hasNext: items.length >= params.perPage,
|
||||
hasPrevious: params.page > 1,
|
||||
);
|
||||
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
if (_useApi) {
|
||||
// API 호출 - 지점 정보 포함
|
||||
print('[CompanyListController] Using API to fetch companies with branches');
|
||||
|
||||
// 지점 정보를 포함한 전체 회사 목록 가져오기
|
||||
final apiCompaniesWithBranches = await _companyService.getCompaniesWithBranchesFlat();
|
||||
|
||||
// 상세한 회사 정보 로그 출력
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 📊 회사 목록 로드 완료');
|
||||
print('║ ▶ 총 회사 수: ${apiCompaniesWithBranches.length}개');
|
||||
print('╟──────────────────────────────────────────────────────────');
|
||||
|
||||
// 지점이 있는 회사와 없는 회사 구분
|
||||
int companiesWithBranches = 0;
|
||||
int totalBranches = 0;
|
||||
|
||||
for (final company in apiCompaniesWithBranches) {
|
||||
if (company.branches?.isNotEmpty ?? false) {
|
||||
companiesWithBranches++;
|
||||
totalBranches += company.branches!.length;
|
||||
print('║ • ${company.name}: ${company.branches!.length}개 지점');
|
||||
}
|
||||
}
|
||||
|
||||
final companiesWithoutBranches = apiCompaniesWithBranches.length - companiesWithBranches;
|
||||
|
||||
print('╟──────────────────────────────────────────────────────────');
|
||||
print('║ 📈 통계');
|
||||
print('║ • 지점이 있는 회사: ${companiesWithBranches}개');
|
||||
print('║ • 지점이 없는 회사: ${companiesWithoutBranches}개');
|
||||
print('║ • 총 지점 수: ${totalBranches}개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
|
||||
// 검색어 필터 적용 (서버에서 필터링이 안 되므로 클라이언트에서 처리)
|
||||
List<Company> filteredApiCompanies = apiCompaniesWithBranches;
|
||||
if (searchKeyword.isNotEmpty) {
|
||||
final keyword = searchKeyword.toLowerCase();
|
||||
filteredApiCompanies = apiCompaniesWithBranches.where((company) {
|
||||
return company.name.toLowerCase().contains(keyword) ||
|
||||
(company.contactName?.toLowerCase().contains(keyword) ?? false) ||
|
||||
(company.contactPhone?.toLowerCase().contains(keyword) ?? false);
|
||||
}).toList();
|
||||
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🔍 검색 필터 적용');
|
||||
print('║ • 검색어: "$searchKeyword"');
|
||||
print('║ • 필터 전: ${apiCompaniesWithBranches.length}개');
|
||||
print('║ • 필터 후: ${filteredApiCompanies.length}개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
}
|
||||
|
||||
// 활성 상태 필터 적용 (현재 API에서 지원하지 않으므로 주석 처리)
|
||||
// if (_isActiveFilter != null) {
|
||||
// filteredApiCompanies = filteredApiCompanies.where((c) => c.isActive == _isActiveFilter).toList();
|
||||
// }
|
||||
|
||||
// 전체 데이터를 한 번에 로드 (View에서 페이지네이션 처리)
|
||||
companies = filteredApiCompanies;
|
||||
_hasMore = false; // 전체 데이터를 로드했으므로 더 이상 로드할 필요 없음
|
||||
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 📑 전체 데이터 로드 완료');
|
||||
print('║ • 로드된 회사 수: ${companies.length}개');
|
||||
print('║ • 필터링된 회사 수: ${filteredApiCompanies.length}개');
|
||||
print('║ • View에서 페이지네이션 처리 예정');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
} else {
|
||||
// Mock 데이터 사용
|
||||
companies = dataService.getAllCompanies();
|
||||
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🔧 Mock 데이터 로드 완료');
|
||||
print('║ ▶ 총 회사 수: ${companies.length}개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
|
||||
_hasMore = false;
|
||||
}
|
||||
|
||||
// 필터 적용
|
||||
applyFilters();
|
||||
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ ✅ 최종 화면 표시');
|
||||
print('║ • 화면에 표시될 회사 수: ${filteredCompanies.length}개');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
|
||||
selectedCompanyIds.clear();
|
||||
} on Failure catch (e) {
|
||||
print('[CompanyListController] Failure loading companies: ${e.message}');
|
||||
_error = e.message;
|
||||
} catch (e, stackTrace) {
|
||||
print('[CompanyListController] Error loading companies: $e');
|
||||
print('[CompanyListController] Error type: ${e.runtimeType}');
|
||||
print('[CompanyListController] Stack trace: $stackTrace');
|
||||
_error = '회사 목록을 불러오는 중 오류가 발생했습니다: $e';
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
return PagedResult(items: items, meta: meta);
|
||||
}
|
||||
|
||||
// 검색 및 필터 적용
|
||||
void applyFilters() {
|
||||
filteredCompanies = companies.where((company) {
|
||||
// 검색어 필터
|
||||
if (searchKeyword.isNotEmpty) {
|
||||
final keyword = searchKeyword.toLowerCase();
|
||||
final matchesName = company.name.toLowerCase().contains(keyword);
|
||||
final matchesContact = company.contactName?.toLowerCase().contains(keyword) ?? false;
|
||||
final matchesPhone = company.contactPhone?.toLowerCase().contains(keyword) ?? false;
|
||||
|
||||
if (!matchesName && !matchesContact && !matchesPhone) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 활성 상태 필터 (API 사용 시에는 서버에서 필터링되므로 여기서는 Mock 데이터용)
|
||||
if (_isActiveFilter != null && !_useApi) {
|
||||
// Mock 데이터에는 isActive 필드가 없으므로 모두 활성으로 간주
|
||||
if (_isActiveFilter == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}).toList();
|
||||
@override
|
||||
bool filterItem(Company item, String query) {
|
||||
final q = query.toLowerCase();
|
||||
return item.name.toLowerCase().contains(q) ||
|
||||
(item.contactPhone?.toLowerCase().contains(q) ?? false) ||
|
||||
(item.contactEmail?.toLowerCase().contains(q) ?? false) ||
|
||||
(item.companyTypes.any((type) => type.name.toLowerCase().contains(q))) ||
|
||||
(item.address.toString().toLowerCase().contains(q));
|
||||
}
|
||||
|
||||
// 검색어 변경
|
||||
Future<void> updateSearchKeyword(String keyword) async {
|
||||
searchKeyword = keyword;
|
||||
|
||||
if (keyword.isNotEmpty) {
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🔍 검색어 변경: "$keyword"');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
} else {
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ ❌ 검색어 초기화');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
}
|
||||
|
||||
if (_useApi) {
|
||||
// API 사용 시 새로 조회
|
||||
await loadData(isRefresh: true);
|
||||
} else {
|
||||
// Mock 데이터 사용 시 필터만 적용
|
||||
applyFilters();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 활성 상태 필터 변경
|
||||
Future<void> changeActiveFilter(bool? isActive) async {
|
||||
_isActiveFilter = isActive;
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 회사 선택/해제
|
||||
void toggleCompanySelection(int? companyId) {
|
||||
if (companyId == null) return;
|
||||
|
||||
// 회사 선택/선택 해제
|
||||
void toggleSelection(int companyId) {
|
||||
if (selectedCompanyIds.contains(companyId)) {
|
||||
selectedCompanyIds.remove(companyId);
|
||||
} else {
|
||||
@@ -245,119 +98,73 @@ class CompanyListController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 전체 선택/해제
|
||||
void toggleSelectAll() {
|
||||
if (selectedCompanyIds.length == filteredCompanies.length) {
|
||||
selectedCompanyIds.clear();
|
||||
} else {
|
||||
selectedCompanyIds.clear();
|
||||
for (final company in filteredCompanies) {
|
||||
if (company.id != null) {
|
||||
selectedCompanyIds.add(company.id!);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 모든 선택 해제
|
||||
void clearSelection() {
|
||||
selectedCompanyIds.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 선택된 회사 수 반환
|
||||
int getSelectedCount() {
|
||||
return selectedCompanyIds.length;
|
||||
// 필터 설정
|
||||
void setFilters({bool? isActive, CompanyType? type}) {
|
||||
_isActiveFilter = isActive;
|
||||
_typeFilter = type;
|
||||
loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 필터 초기화
|
||||
void clearFilters() {
|
||||
_isActiveFilter = null;
|
||||
_typeFilter = null;
|
||||
search('');
|
||||
loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 회사 추가
|
||||
Future<void> addCompany(Company company) async {
|
||||
await ErrorHandler.handleApiCall<void>(
|
||||
() => _companyService.createCompany(company),
|
||||
onError: (failure) {
|
||||
throw failure;
|
||||
},
|
||||
);
|
||||
|
||||
await refresh();
|
||||
}
|
||||
|
||||
// 회사 수정
|
||||
Future<void> updateCompany(Company company) async {
|
||||
if (company.id == null) {
|
||||
throw Exception('회사 ID가 없습니다');
|
||||
}
|
||||
|
||||
await ErrorHandler.handleApiCall<void>(
|
||||
() => _companyService.updateCompany(company.id!, company),
|
||||
onError: (failure) {
|
||||
throw failure;
|
||||
},
|
||||
);
|
||||
|
||||
updateItemLocally(company, (c) => c.id == company.id);
|
||||
}
|
||||
|
||||
// 회사 삭제
|
||||
Future<bool> deleteCompany(int companyId) async {
|
||||
try {
|
||||
if (_useApi) {
|
||||
// API를 통한 삭제
|
||||
await _companyService.deleteCompany(companyId);
|
||||
} else {
|
||||
// Mock 데이터 삭제
|
||||
dataService.deleteCompany(companyId);
|
||||
}
|
||||
|
||||
// 로컬 리스트에서도 제거
|
||||
companies.removeWhere((c) => c.id == companyId);
|
||||
filteredCompanies.removeWhere((c) => c.id == companyId);
|
||||
selectedCompanyIds.remove(companyId);
|
||||
notifyListeners();
|
||||
|
||||
return true;
|
||||
} on Failure catch (e) {
|
||||
_error = e.message;
|
||||
notifyListeners();
|
||||
return false;
|
||||
} catch (e) {
|
||||
_error = '회사 삭제 중 오류가 발생했습니다: $e';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
Future<void> deleteCompany(int id) async {
|
||||
await ErrorHandler.handleApiCall<void>(
|
||||
() => _companyService.deleteCompany(id),
|
||||
onError: (failure) {
|
||||
throw failure;
|
||||
},
|
||||
);
|
||||
|
||||
removeItemLocally((c) => c.id == id);
|
||||
selectedCompanyIds.remove(id);
|
||||
}
|
||||
|
||||
// 선택된 회사들 삭제
|
||||
Future<bool> deleteSelectedCompanies() async {
|
||||
final selectedIds = selectedCompanyIds.toList();
|
||||
int successCount = 0;
|
||||
|
||||
for (final companyId in selectedIds) {
|
||||
if (await deleteCompany(companyId)) {
|
||||
successCount++;
|
||||
}
|
||||
Future<void> deleteSelectedCompanies() async {
|
||||
for (final id in selectedCompanyIds.toList()) {
|
||||
await deleteCompany(id);
|
||||
}
|
||||
|
||||
return successCount == selectedIds.length;
|
||||
}
|
||||
|
||||
// 회사 정보 업데이트 (로컬)
|
||||
void updateCompanyLocally(Company updatedCompany) {
|
||||
final index = companies.indexWhere((c) => c.id == updatedCompany.id);
|
||||
if (index != -1) {
|
||||
companies[index] = updatedCompany;
|
||||
applyFilters();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 회사 추가 (로컬)
|
||||
void addCompanyLocally(Company newCompany) {
|
||||
companies.insert(0, newCompany);
|
||||
applyFilters();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 더 많은 데이터 로드
|
||||
Future<void> loadMore() async {
|
||||
print('🔍 [DEBUG] loadMore 호출됨 - hasMore: $_hasMore, isLoading: $_isLoading, useApi: $_useApi');
|
||||
if (!_hasMore || _isLoading || !_useApi) {
|
||||
print('🔍 [DEBUG] loadMore 조건 미충족으로 종료 (hasMore: $_hasMore, isLoading: $_isLoading, useApi: $_useApi)');
|
||||
return;
|
||||
}
|
||||
print('🔍 [DEBUG] loadMore 실행 - 추가 데이터 로드 시작');
|
||||
await loadData();
|
||||
}
|
||||
|
||||
// API 사용 여부 토글 (테스트용)
|
||||
void toggleApiUsage() {
|
||||
_useApi = !_useApi;
|
||||
loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 에러 처리
|
||||
void clearError() {
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 리프레시
|
||||
Future<void> refresh() async {
|
||||
print('╔══════════════════════════════════════════════════════════');
|
||||
print('║ 🔄 회사 목록 새로고침 시작');
|
||||
print('╚══════════════════════════════════════════════════════════');
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
clearSelection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/controllers/base_list_controller.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../../../domain/usecases/base_usecase.dart';
|
||||
import '../../../domain/usecases/company/company_usecases.dart';
|
||||
import '../../../models/company_model.dart';
|
||||
import '../../../services/company_service.dart';
|
||||
import '../../../di/injection_container.dart';
|
||||
import '../../../data/models/common/pagination_params.dart';
|
||||
|
||||
/// UseCase를 활용한 회사 목록 관리 컨트롤러
|
||||
/// BaseListController를 상속받아 공통 기능 재사용
|
||||
class CompanyListControllerWithUseCase extends BaseListController<Company> {
|
||||
// UseCases
|
||||
late final GetCompaniesUseCase _getCompaniesUseCase;
|
||||
late final CreateCompanyUseCase _createCompanyUseCase;
|
||||
late final UpdateCompanyUseCase _updateCompanyUseCase;
|
||||
late final DeleteCompanyUseCase _deleteCompanyUseCase;
|
||||
late final GetCompanyDetailUseCase _getCompanyDetailUseCase;
|
||||
late final ToggleCompanyStatusUseCase _toggleCompanyStatusUseCase;
|
||||
|
||||
// 필터 상태
|
||||
String? selectedType;
|
||||
bool? isActive;
|
||||
|
||||
// 선택된 회사들
|
||||
final Set<int> _selectedCompanyIds = {};
|
||||
Set<int> get selectedCompanyIds => _selectedCompanyIds;
|
||||
bool get hasSelection => _selectedCompanyIds.isNotEmpty;
|
||||
|
||||
CompanyListControllerWithUseCase() {
|
||||
// UseCase 초기화
|
||||
final companyService = inject<CompanyService>();
|
||||
_getCompaniesUseCase = GetCompaniesUseCase(companyService);
|
||||
_createCompanyUseCase = CreateCompanyUseCase(companyService);
|
||||
_updateCompanyUseCase = UpdateCompanyUseCase(companyService);
|
||||
_deleteCompanyUseCase = DeleteCompanyUseCase(companyService);
|
||||
_getCompanyDetailUseCase = GetCompanyDetailUseCase(companyService);
|
||||
_toggleCompanyStatusUseCase = ToggleCompanyStatusUseCase(companyService);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PagedResult<Company>> fetchData({
|
||||
required PaginationParams params,
|
||||
Map<String, dynamic>? additionalFilters,
|
||||
}) async {
|
||||
// UseCase를 통한 데이터 조회
|
||||
final usecaseParams = GetCompaniesParams(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
search: params.search,
|
||||
isActive: isActive,
|
||||
);
|
||||
|
||||
final result = await _getCompaniesUseCase(usecaseParams);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
throw Exception(failure.message);
|
||||
},
|
||||
(companies) {
|
||||
// PagedResult로 래핑하여 반환 (임시로 메타데이터 생성)
|
||||
final meta = PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: companies.length, // 실제로는 서버에서 받아와야 함
|
||||
totalPages: (companies.length / params.perPage).ceil(),
|
||||
hasNext: companies.length >= params.perPage,
|
||||
hasPrevious: params.page > 1,
|
||||
);
|
||||
return PagedResult(items: companies, meta: meta);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 생성
|
||||
Future<bool> createCompany(Company company) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
final params = CreateCompanyParams(company: company);
|
||||
final result = await _createCompanyUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
|
||||
// ValidationFailure의 경우 상세 에러 표시
|
||||
if (failure is ValidationFailure && failure.errors != null) {
|
||||
final errorMessages = failure.errors!.entries
|
||||
.map((e) => '${e.key}: ${e.value}')
|
||||
.join('\n');
|
||||
errorState = errorMessages;
|
||||
}
|
||||
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(newCompany) {
|
||||
// 로컬 리스트에 추가
|
||||
addItemLocally(newCompany);
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 수정
|
||||
Future<bool> updateCompany(int id, Company company) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
final params = UpdateCompanyParams(id: id, company: company);
|
||||
final result = await _updateCompanyUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
|
||||
// ValidationFailure의 경우 상세 에러 표시
|
||||
if (failure is ValidationFailure && failure.errors != null) {
|
||||
final errorMessages = failure.errors!.entries
|
||||
.map((e) => '${e.key}: ${e.value}')
|
||||
.join('\n');
|
||||
errorState = errorMessages;
|
||||
}
|
||||
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(updatedCompany) {
|
||||
// 로컬 리스트 업데이트
|
||||
updateItemLocally(updatedCompany, (item) => item.id == id);
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 삭제
|
||||
Future<bool> deleteCompany(int id) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
final params = DeleteCompanyParams(id: id);
|
||||
final result = await _deleteCompanyUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(_) {
|
||||
// 로컬 리스트에서 제거
|
||||
removeItemLocally((item) => item.id == id);
|
||||
_selectedCompanyIds.remove(id);
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 상세 조회
|
||||
Future<Company?> getCompanyDetail(int id, {bool includeBranches = false}) async {
|
||||
final params = GetCompanyDetailParams(
|
||||
id: id,
|
||||
includeBranches: includeBranches,
|
||||
);
|
||||
|
||||
final result = await _getCompanyDetailUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
notifyListeners();
|
||||
return null;
|
||||
},
|
||||
(company) => company,
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 상태 토글 (활성화/비활성화)
|
||||
Future<bool> toggleCompanyStatus(int id) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
// 현재 회사 상태를 확인하여 토글 (기본값 true로 가정)
|
||||
final params = ToggleCompanyStatusParams(
|
||||
id: id,
|
||||
isActive: false, // 임시로 false로 설정 (실제로는 현재 상태를 API로 확인해야 함)
|
||||
);
|
||||
final result = await _toggleCompanyStatusUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(_) {
|
||||
// 로컬 리스트에서 상태 업데이트 (실제로는 API에서 업데이트된 Company 객체를 받아와야 함)
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 선택/해제
|
||||
void toggleSelection(int companyId) {
|
||||
if (_selectedCompanyIds.contains(companyId)) {
|
||||
_selectedCompanyIds.remove(companyId);
|
||||
} else {
|
||||
_selectedCompanyIds.add(companyId);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 전체 선택/해제
|
||||
void toggleSelectAll() {
|
||||
if (_selectedCompanyIds.length == items.length) {
|
||||
_selectedCompanyIds.clear();
|
||||
} else {
|
||||
_selectedCompanyIds.clear();
|
||||
_selectedCompanyIds.addAll(items.where((c) => c.id != null).map((c) => c.id!));
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 선택 초기화
|
||||
void clearSelection() {
|
||||
_selectedCompanyIds.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 필터 적용
|
||||
void applyFilters({String? type, bool? active}) {
|
||||
selectedType = type;
|
||||
isActive = active;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// 필터 초기화
|
||||
void clearFilters() {
|
||||
selectedType = null;
|
||||
isActive = null;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// 선택된 회사들 일괄 삭제
|
||||
Future<bool> deleteSelectedCompanies() async {
|
||||
if (_selectedCompanyIds.isEmpty) return false;
|
||||
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
bool allSuccess = true;
|
||||
final failedIds = <int>[];
|
||||
|
||||
for (final id in _selectedCompanyIds.toList()) {
|
||||
final params = DeleteCompanyParams(id: id);
|
||||
final result = await _deleteCompanyUseCase(params);
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
allSuccess = false;
|
||||
failedIds.add(id);
|
||||
debugPrint('회사 $id 삭제 실패: ${failure.message}');
|
||||
},
|
||||
(_) {
|
||||
removeItemLocally((item) => item.id == id);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (failedIds.isNotEmpty) {
|
||||
errorState = '일부 회사 삭제 실패: ${failedIds.join(', ')}';
|
||||
}
|
||||
|
||||
_selectedCompanyIds.clear();
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
|
||||
return allSuccess;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user