feat: 회사 관리 API 연동 완료

- CompanyListController 생성 (ChangeNotifier 패턴)
- CompanyListRedesign 화면 Provider 패턴으로 변경
- 무한 스크롤 및 실시간 검색 기능 구현 (디바운싱 적용)
- 중복 회사명 체크 API 연동
- 지점 저장 로직 API 연동 (saveBranch 메서드 추가)
- 에러 처리 및 로딩 상태 UI 구현
- API 통합 계획 문서 업데이트 (회사 관리 100% 완료)
This commit is contained in:
JiWoong Sul
2025-07-24 18:15:50 +09:00
parent 6b31631cfb
commit 7f491afa4f
5 changed files with 578 additions and 159 deletions

View File

@@ -0,0 +1,261 @@
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';
// 회사 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
class CompanyListController extends ChangeNotifier {
final MockDataService dataService;
final CompanyService _companyService = GetIt.instance<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;
final 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({required this.dataService});
// 초기 데이터 로드
Future<void> initialize() async {
await loadData(isRefresh: true);
}
// 데이터 로드 및 필터 적용
Future<void> loadData({bool isRefresh = false}) async {
if (isRefresh) {
_currentPage = 1;
_hasMore = true;
companies.clear();
filteredCompanies.clear();
}
if (_isLoading || (!_hasMore && !isRefresh)) return;
_isLoading = true;
_error = null;
notifyListeners();
try {
if (_useApi) {
// API 호출
final apiCompanies = await _companyService.getCompanies(
page: _currentPage,
perPage: _perPage,
search: searchKeyword.isNotEmpty ? searchKeyword : null,
isActive: _isActiveFilter,
);
if (isRefresh) {
companies = apiCompanies;
} else {
companies.addAll(apiCompanies);
}
_hasMore = apiCompanies.length == _perPage;
if (_hasMore) _currentPage++;
} else {
// Mock 데이터 사용
companies = dataService.getAllCompanies();
_hasMore = false;
}
// 필터 적용
applyFilters();
selectedCompanyIds.clear();
} on Failure catch (e) {
_error = e.message;
} catch (e) {
_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 사용 시에는 서버에서 필터링되므로 여기서는 Mock 데이터용)
if (_isActiveFilter != null && !_useApi) {
// Mock 데이터에는 isActive 필드가 없으므로 모두 활성으로 간주
if (_isActiveFilter == false) {
return false;
}
}
return true;
}).toList();
}
// 검색어 변경
Future<void> updateSearchKeyword(String keyword) async {
searchKeyword = keyword;
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;
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 {
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<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 {
if (!_hasMore || _isLoading || !_useApi) return;
await loadData();
}
// API 사용 여부 토글 (테스트용)
void toggleApiUsage() {
_useApi = !_useApi;
loadData(isRefresh: true);
}
// 에러 처리
void clearError() {
_error = null;
notifyListeners();
}
// 리프레시
Future<void> refresh() async {
await loadData(isRefresh: true);
}
@override
void dispose() {
super.dispose();
}
}