## 주요 변경사항 ### 아키텍처 개선 - Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리) - Use Case 패턴 도입으로 비즈니스 로직 캡슐화 - Repository 패턴으로 데이터 접근 추상화 - 의존성 주입 구조 개선 ### 상태 관리 최적화 - 모든 Controller에서 불필요한 상태 관리 로직 제거 - 페이지네이션 로직 통일 및 간소화 - 에러 처리 로직 개선 (에러 메시지 한글화) - 로딩 상태 관리 최적화 ### Mock 서비스 제거 - MockDataService 완전 제거 - 모든 화면을 실제 API 전용으로 전환 - 불필요한 Mock 관련 코드 정리 ### UI/UX 개선 - Overview 화면 대시보드 기능 강화 - 라이선스 만료 알림 위젯 추가 - 사이드바 네비게이션 개선 - 일관된 UI 컴포넌트 사용 ### 코드 품질 - 중복 코드 제거 및 함수 추출 - 파일별 책임 분리 명확화 - 테스트 코드 업데이트 ## 영향 범위 - 모든 화면의 Controller 리팩토링 - API 통신 레이어 구조 개선 - 에러 처리 및 로깅 시스템 개선 ## 향후 계획 - 단위 테스트 커버리지 확대 - 통합 테스트 시나리오 추가 - 성능 모니터링 도구 통합
562 lines
17 KiB
Dart
562 lines
17 KiB
Dart
/// 회사 폼 컨트롤러
|
||
///
|
||
/// 회사 폼 화면의 비즈니스 로직을 담당하는 컨트롤러 클래스
|
||
/// 주요 기능:
|
||
/// - 회사 데이터 로드 및 저장
|
||
/// - 자동완성 처리
|
||
/// - 지점 정보 관리 (추가, 삭제, 수정)
|
||
/// - 전화번호 처리
|
||
/// - 중복 회사명 체크
|
||
/// - 회사 유형 관리
|
||
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'; // Mock 서비스 제거
|
||
import 'package:superport/services/company_service.dart';
|
||
import 'package:superport/core/errors/failures.dart';
|
||
import 'package:superport/utils/phone_utils.dart';
|
||
import 'dart:async';
|
||
import 'branch_form_controller.dart'; // 분리된 지점 컨트롤러 import
|
||
|
||
/// 회사 폼 컨트롤러 - 비즈니스 로직 처리
|
||
class CompanyFormController {
|
||
// final MockDataService? dataService; // Mock 서비스 제거
|
||
final CompanyService _companyService = GetIt.instance<CompanyService>();
|
||
final int? companyId;
|
||
|
||
// Feature flag for API usage
|
||
final bool _useApi;
|
||
|
||
final TextEditingController nameController = TextEditingController();
|
||
Address companyAddress = const Address();
|
||
final TextEditingController contactNameController = TextEditingController();
|
||
final TextEditingController contactPositionController =
|
||
TextEditingController();
|
||
final TextEditingController contactPhoneController = TextEditingController();
|
||
final TextEditingController contactEmailController = TextEditingController();
|
||
final TextEditingController remarkController = TextEditingController();
|
||
final FocusNode nameFocusNode = FocusNode();
|
||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||
final ScrollController scrollController = ScrollController();
|
||
|
||
// 회사 유형 선택값 (복수)
|
||
List<CompanyType> selectedCompanyTypes = [CompanyType.customer];
|
||
|
||
List<String> companyNames = [];
|
||
List<String> filteredCompanyNames = [];
|
||
bool showCompanyNameDropdown = false;
|
||
|
||
// 분리된 BranchFormController 리스트로 관리
|
||
List<BranchFormController> branchControllers = [];
|
||
|
||
// 직책 목록 및 전화번호 접두사 목록(공통 상수)
|
||
final List<String> positions = [
|
||
'대표이사',
|
||
'사장',
|
||
'부사장',
|
||
'전무',
|
||
'상무',
|
||
'이사',
|
||
'부장',
|
||
'차장',
|
||
'팀장',
|
||
'과장',
|
||
'대리',
|
||
'사원',
|
||
'주임',
|
||
'기타',
|
||
];
|
||
final List<String> phonePrefixes = getCommonPhonePrefixes();
|
||
|
||
String selectedPhonePrefix = '010';
|
||
final List<String> phonePrefixesForMain = getCommonPhonePrefixes();
|
||
|
||
ValueNotifier<bool> showPositionDropdownNotifier = ValueNotifier<bool>(false);
|
||
Timer? debounceTimer;
|
||
bool preventAutoFocus = false;
|
||
final Map<int, bool> isNewlyAddedBranch = {};
|
||
|
||
CompanyFormController({this.companyId, bool useApi = true})
|
||
: _useApi = useApi {
|
||
_setupFocusNodes();
|
||
_setupControllerListeners();
|
||
// 비동기 초기화는 별도로 호출해야 함
|
||
Future.microtask(() => _initializeAsync());
|
||
}
|
||
|
||
Future<void> _initializeAsync() async {
|
||
final isEditMode = companyId != null;
|
||
await _loadCompanyNames();
|
||
// loadCompanyData는 별도로 호출됨 (company_form.dart에서)
|
||
}
|
||
|
||
// 회사명 목록 로드 (자동완성용)
|
||
Future<void> _loadCompanyNames() async {
|
||
try {
|
||
List<Company> companies;
|
||
|
||
// API만 사용
|
||
companies = await _companyService.getCompanies();
|
||
|
||
companyNames = companies.map((c) => c.name).toList();
|
||
filteredCompanyNames = companyNames;
|
||
} catch (e) {
|
||
debugPrint('❌ 회사명 목록 로드 실패: $e');
|
||
companyNames = [];
|
||
filteredCompanyNames = [];
|
||
}
|
||
}
|
||
|
||
// 회사 데이터 로드 (수정 모드)
|
||
Future<void> loadCompanyData() async {
|
||
if (companyId == null) return;
|
||
|
||
debugPrint('📝 loadCompanyData 시작 - ID: $companyId');
|
||
|
||
try {
|
||
Company? company;
|
||
|
||
if (_useApi) {
|
||
debugPrint('📝 API에서 회사 정보 로드 중...');
|
||
company = await _companyService.getCompanyDetail(companyId!);
|
||
} else {
|
||
debugPrint('📝 API만 사용 가능');
|
||
throw Exception('API를 통해만 데이터를 로드할 수 있습니다');
|
||
}
|
||
|
||
debugPrint('📝 로드된 회사: $company');
|
||
|
||
if (company != null) {
|
||
// 폼 필드에 데이터 설정
|
||
debugPrint('📝 회사명 설정 전: "${nameController.text}"');
|
||
nameController.text = company.name;
|
||
debugPrint('📝 회사명 설정 후: "${nameController.text}"');
|
||
|
||
companyAddress = company.address;
|
||
debugPrint('📝 주소 설정: $companyAddress');
|
||
|
||
contactNameController.text = company.contactName ?? '';
|
||
debugPrint('📝 담당자명 설정: "${contactNameController.text}"');
|
||
|
||
contactPositionController.text = company.contactPosition ?? '';
|
||
debugPrint('📝 직급 설정: "${contactPositionController.text}"');
|
||
|
||
contactEmailController.text = company.contactEmail ?? '';
|
||
debugPrint('📝 이메일 설정: "${contactEmailController.text}"');
|
||
|
||
remarkController.text = company.remark ?? '';
|
||
debugPrint('📝 비고 설정: "${remarkController.text}"');
|
||
|
||
// 전화번호 처리
|
||
if (company.contactPhone != null && company.contactPhone!.isNotEmpty) {
|
||
selectedPhonePrefix = extractPhonePrefix(
|
||
company.contactPhone!,
|
||
phonePrefixes,
|
||
);
|
||
contactPhoneController.text = extractPhoneNumberWithoutPrefix(
|
||
company.contactPhone!,
|
||
phonePrefixes,
|
||
);
|
||
}
|
||
|
||
// 회사 유형 설정
|
||
selectedCompanyTypes = company.companyTypes;
|
||
|
||
// 지점 정보 설정
|
||
if (company.branches != null && company.branches!.isNotEmpty) {
|
||
branchControllers.clear();
|
||
for (final branch in company.branches!) {
|
||
branchControllers.add(
|
||
BranchFormController(
|
||
branch: branch,
|
||
positions: positions,
|
||
phonePrefixes: phonePrefixes,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
debugPrint('📝 폼 필드 설정 완료:');
|
||
debugPrint(' - 회사명: ${nameController.text}');
|
||
debugPrint(' - 담당자: ${contactNameController.text}');
|
||
debugPrint(' - 이메일: ${contactEmailController.text}');
|
||
debugPrint(' - 지점 수: ${branchControllers.length}');
|
||
}
|
||
} catch (e) {
|
||
debugPrint('❌ 회사 정보 로드 실패: $e');
|
||
}
|
||
}
|
||
|
||
void dispose() {
|
||
debounceTimer?.cancel();
|
||
scrollController.dispose();
|
||
showPositionDropdownNotifier.dispose();
|
||
for (final branchController in branchControllers) {
|
||
branchController.dispose();
|
||
}
|
||
nameController.dispose();
|
||
contactNameController.dispose();
|
||
contactPositionController.dispose();
|
||
contactPhoneController.dispose();
|
||
contactEmailController.dispose();
|
||
remarkController.dispose();
|
||
nameFocusNode.dispose();
|
||
}
|
||
|
||
void _setupFocusNodes() {
|
||
nameFocusNode.addListener(() {
|
||
if (nameFocusNode.hasFocus) {
|
||
showCompanyNameDropdown = filteredCompanyNames.isNotEmpty;
|
||
} else {
|
||
showCompanyNameDropdown = false;
|
||
}
|
||
});
|
||
}
|
||
|
||
void _setupControllerListeners() {
|
||
nameController.addListener(_onCompanyNameTextChanged);
|
||
}
|
||
|
||
Future<void> _loadCompanyData() async {
|
||
if (companyId == null) return;
|
||
|
||
Company? company;
|
||
if (_useApi) {
|
||
try {
|
||
company = await _companyService.getCompanyWithBranches(companyId!);
|
||
} on Failure catch (e) {
|
||
debugPrint('Failed to load company data: ${e.message}');
|
||
return;
|
||
}
|
||
} else {
|
||
// API만 사용
|
||
debugPrint('API를 통해만 데이터를 로드할 수 있습니다');
|
||
}
|
||
|
||
if (company != null) {
|
||
nameController.text = company.name;
|
||
companyAddress = company.address;
|
||
selectedCompanyTypes = List.from(company.companyTypes); // 복수 유형 지원
|
||
contactNameController.text = company.contactName ?? '';
|
||
contactPositionController.text = company.contactPosition ?? '';
|
||
selectedPhonePrefix = extractPhonePrefix(
|
||
company.contactPhone ?? '',
|
||
phonePrefixesForMain,
|
||
);
|
||
contactPhoneController.text = extractPhoneNumberWithoutPrefix(
|
||
company.contactPhone ?? '',
|
||
phonePrefixesForMain,
|
||
);
|
||
contactEmailController.text = company.contactEmail ?? '';
|
||
remarkController.text = company.remark ?? '';
|
||
// 지점 컨트롤러 생성
|
||
branchControllers.clear();
|
||
final branches = company.branches?.toList() ?? [];
|
||
if (branches.isEmpty) {
|
||
_addInitialBranch();
|
||
} else {
|
||
for (final branch in branches) {
|
||
branchControllers.add(
|
||
BranchFormController(
|
||
branch: branch,
|
||
positions: positions,
|
||
phonePrefixes: phonePrefixes,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void _addInitialBranch() {
|
||
final newBranch = Branch(
|
||
companyId: companyId ?? 0,
|
||
name: '본사',
|
||
address: const Address(),
|
||
);
|
||
branchControllers.add(
|
||
BranchFormController(
|
||
branch: newBranch,
|
||
positions: positions,
|
||
phonePrefixes: phonePrefixes,
|
||
),
|
||
);
|
||
isNewlyAddedBranch[branchControllers.length - 1] = true;
|
||
}
|
||
|
||
void updateCompanyAddress(Address address) {
|
||
companyAddress = address;
|
||
}
|
||
|
||
void updateBranchAddress(int index, Address address) {
|
||
if (index >= 0 && index < branchControllers.length) {
|
||
branchControllers[index].updateAddress(address);
|
||
}
|
||
}
|
||
|
||
void _onCompanyNameTextChanged() {
|
||
final query = nameController.text.toLowerCase();
|
||
if (query.isEmpty) {
|
||
filteredCompanyNames = List.from(companyNames);
|
||
} else {
|
||
filteredCompanyNames =
|
||
companyNames
|
||
.where((name) => name.toLowerCase().contains(query))
|
||
.toList();
|
||
}
|
||
showCompanyNameDropdown =
|
||
nameFocusNode.hasFocus && filteredCompanyNames.isNotEmpty;
|
||
}
|
||
|
||
void selectCompanyName(String name) {
|
||
nameController.text = name;
|
||
showCompanyNameDropdown = false;
|
||
}
|
||
|
||
// 지점 추가
|
||
void addBranch() {
|
||
final newBranch = Branch(
|
||
companyId: companyId ?? 0,
|
||
name: '지점 |