## 주요 개선사항 ### 🔧 Flutter Analyze 오류 대폭 개선 - 이전: 47개 이슈 (ERROR 14개 포함) - 현재: 22개 이슈 (ERROR 0개) - 개선율: 53% 감소, 모든 ERROR 해결 ### 🎨 재고 이력 화면 UI 통일 완료 - BaseListScreen 패턴 완전 적용 - 헤더 고정 + 바디 스크롤 구조 구현 - shadcn_ui 컴포넌트 100% 사용 - 장비 관리 화면과 동일한 표준 패턴 ### ✨ 코드 품질 개선 - unused imports 제거 (5개 파일) - unnecessary cast 제거 - unused fields 제거 - injection container 오류 해결 ### 📋 문서화 완료 - CLAUDE.md에 UI 통일성 리팩토링 계획 상세 추가 - 전체 10개 화면의 단계별 계획 문서화 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
628 lines
21 KiB
Dart
628 lines
21 KiB
Dart
/// 회사 폼 컨트롤러
|
||
///
|
||
/// 회사 폼 화면의 비즈니스 로직을 담당하는 컨트롤러 클래스
|
||
/// 주요 기능:
|
||
/// - 회사 데이터 로드 및 저장
|
||
/// - 자동완성 처리
|
||
/// - 지점 정보 관리 (추가, 삭제, 수정)
|
||
/// - 전화번호 처리
|
||
/// - 중복 회사명 체크
|
||
/// - 회사 유형 관리
|
||
library;
|
||
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 'dart:async';
|
||
import 'branch_form_controller.dart'; // 분리된 지점 컨트롤러 import
|
||
import 'package:superport/data/models/zipcode_dto.dart';
|
||
|
||
/// 회사 폼 컨트롤러 - 비즈니스 로직 처리
|
||
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 zipcodeController = TextEditingController();
|
||
ZipcodeDto? selectedZipcode;
|
||
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];
|
||
|
||
// 부모 회사 선택
|
||
int? selectedParentCompanyId;
|
||
List<Company> availableParentCompanies = [];
|
||
|
||
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 {
|
||
await _loadCompanyNames();
|
||
// loadCompanyData는 별도로 호출됨 (company_form.dart에서)
|
||
}
|
||
|
||
// 회사명 목록 로드 (자동완성용)
|
||
Future<void> _loadCompanyNames() async {
|
||
try {
|
||
List<Company> companies;
|
||
|
||
// API만 사용 (PaginatedResponse에서 items 추출)
|
||
final response = await _companyService.getCompanies(page: 1, perPage: 1000);
|
||
companies = response.items;
|
||
|
||
companyNames = companies.map((c) => c.name).toList();
|
||
filteredCompanyNames = companyNames;
|
||
|
||
// 부모 회사 목록도 설정 (자기 자신은 제외)
|
||
if (companyId != null) {
|
||
availableParentCompanies = companies.where((c) => c.id != companyId).toList();
|
||
} else {
|
||
availableParentCompanies = companies;
|
||
}
|
||
} catch (e) {
|
||
debugPrint('❌ 회사명 목록 로드 실패: $e');
|
||
companyNames = [];
|
||
filteredCompanyNames = [];
|
||
availableParentCompanies = [];
|
||
}
|
||
}
|
||
|
||
// 회사 데이터 로드 (수정 모드)
|
||
Future<void> loadCompanyData() async {
|
||
if (companyId == null) {
|
||
debugPrint('❌ companyId가 null입니다');
|
||
return;
|
||
}
|
||
|
||
debugPrint('📝 loadCompanyData 시작 - ID: $companyId');
|
||
|
||
try {
|
||
Company? company;
|
||
|
||
if (_useApi) {
|
||
debugPrint('📝 API에서 회사 정보 로드 중...');
|
||
company = await _companyService.getCompanyDetail(companyId!);
|
||
debugPrint('📝 API 응답 받음: ${company != null ? "성공" : "null"}');
|
||
} else {
|
||
debugPrint('📝 API만 사용 가능');
|
||
throw Exception('API를 통해만 데이터를 로드할 수 있습니다');
|
||
}
|
||
|
||
debugPrint('📝 로드된 회사 정보:');
|
||
debugPrint(' - ID: ${company.id}');
|
||
debugPrint(' - 이름: ${company.name}');
|
||
debugPrint(' - 담당자: ${company.contactName}');
|
||
debugPrint(' - 연락처: ${company.contactPhone}');
|
||
debugPrint(' - 이메일: ${company.contactEmail}');
|
||
|
||
// 폼 필드에 데이터 설정
|
||
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,
|
||
);
|
||
debugPrint('📝 전화번호 설정: $selectedPhonePrefix-${contactPhoneController.text}');
|
||
}
|
||
|
||
// 회사 유형 설정
|
||
selectedCompanyTypes = List.from(company.companyTypes);
|
||
debugPrint('📝 회사 유형 설정: $selectedCompanyTypes');
|
||
|
||
// 부모 회사 설정
|
||
selectedParentCompanyId = company.parentCompanyId;
|
||
debugPrint('📝 부모 회사 설정: $selectedParentCompanyId');
|
||
|
||
// 지점 정보 설정
|
||
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('📝 지점 설정 완료: ${branchControllers.length}개');
|
||
}
|
||
|
||
debugPrint('📝 폼 필드 설정 완료:');
|
||
debugPrint(' - 회사명: "${nameController.text}"');
|
||
debugPrint(' - 담당자: "${contactNameController.text}"');
|
||
debugPrint(' - 이메일: "${contactEmailController.text}"');
|
||
debugPrint(' - 전화번호: "$selectedPhonePrefix-${contactPhoneController.text}"');
|
||
debugPrint(' - 지점 수: ${branchControllers.length}');
|
||
debugPrint(' - 회사 유형: $selectedCompanyTypes');
|
||
|
||
// TextEditingController는 text 설정 시 자동으로 리스너 트리거됨
|
||
// notifyListeners() 직접 호출은 불필요하고 부적절함
|
||
|
||
debugPrint('✅ 폼 데이터 로드 완료');
|
||
} catch (e, stackTrace) {
|
||
debugPrint('❌ 회사 정보 로드 실패: $e');
|
||
debugPrint('❌ 스택 트레이스: $stackTrace');
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
|
||
|
||
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: '지점 |