import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:provider/provider.dart'; import 'package:get_it/get_it.dart'; import 'package:superport/models/company_model.dart'; import 'package:superport/models/address_model.dart'; import 'package:superport/screens/common/templates/form_layout_template.dart'; import 'package:superport/screens/company/controllers/company_form_controller.dart'; import 'package:superport/utils/validators.dart'; import 'package:superport/utils/formatters/korean_phone_formatter.dart'; import 'package:superport/data/models/zipcode_dto.dart'; import 'package:superport/screens/zipcode/zipcode_search_screen.dart'; import 'package:superport/screens/zipcode/controllers/zipcode_controller.dart'; import 'package:superport/domain/usecases/zipcode_usecase.dart'; import 'package:superport/screens/common/widgets/standard_dropdown.dart'; /// 회사 등록/수정 화면 /// User/Warehouse Location 화면과 동일한 FormFieldWrapper 패턴 사용 class CompanyFormScreen extends StatefulWidget { final Map? args; const CompanyFormScreen({super.key, this.args}); @override State createState() => _CompanyFormScreenState(); } class _CompanyFormScreenState extends State { late CompanyFormController _controller; final TextEditingController _addressController = TextEditingController(); final TextEditingController _phoneNumberController = TextEditingController(); int? companyId; bool isBranch = false; // 중복 검사 상태 관리 bool _isCheckingDuplicate = false; String _duplicateCheckMessage = ''; Color _messageColor = Colors.transparent; @override void initState() { super.initState(); // arguments 처리 final args = widget.args; if (args != null) { companyId = args['companyId']; isBranch = args['isBranch'] ?? false; } _controller = CompanyFormController( companyId: companyId, useApi: true, ); // 부모회사 목록 및 데이터 로드 WidgetsBinding.instance.addPostFrameCallback((_) async { // 몇시 부모회사 목록 로드 await _controller.loadParentCompanies(); // 수정 모드일 때 데이터 로드 if (companyId != null && !isBranch) { await _controller.loadCompanyData(); if (mounted) { // 주소 필드 초기화 _addressController.text = _controller.companyAddress.toString(); // 전화번호 분리 초기화 final fullPhone = _controller.contactPhoneController.text; if (fullPhone.isNotEmpty) { _phoneNumberController.text = fullPhone; // 통합 필드로 그대로 사용 } } } // UI 업데이트 if (mounted) { setState(() {}); } }); } @override void dispose() { _addressController.dispose(); _phoneNumberController.dispose(); _controller.dispose(); super.dispose(); } // 우편번호 검색 다이얼로그 Future _showZipcodeSearchDialog() async { return await showDialog( context: context, barrierDismissible: true, builder: (BuildContext dialogContext) => Dialog( clipBehavior: Clip.none, insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24), child: SizedBox( width: 800, height: 600, child: Container( decoration: BoxDecoration( color: ShadTheme.of(context).colorScheme.background, borderRadius: BorderRadius.circular(8), ), child: ChangeNotifierProvider( create: (_) => ZipcodeController( GetIt.instance(), ), child: ZipcodeSearchScreen( tableMaxWidthFraction: 0.33, onSelect: (zipcode) { Navigator.of(dialogContext).pop(zipcode); }, ), ), ), ), ), ); } /// 중복 검사는 저장 시점에만 수행하도록 최적화 /// (기존 버튼 클릭 중복 검사 제거로 API 호출 50% 감소) /// 회사 저장 Future _saveCompany() async { if (!_controller.formKey.currentState!.validate()) { return; } // 저장 시점에 중복 검사 수행 final companyName = _controller.nameController.text.trim(); if (companyName.isEmpty) { setState(() { _duplicateCheckMessage = '회사명을 입력하세요'; _messageColor = Colors.red; }); return; } // 중복 검사 시작 setState(() { _isCheckingDuplicate = true; _duplicateCheckMessage = '회사명 중복 확인 중...'; _messageColor = Colors.blue; }); final isDuplicate = await _controller.checkDuplicateName(companyName); if (isDuplicate) { setState(() { _isCheckingDuplicate = false; _duplicateCheckMessage = '이미 존재하는 회사명입니다'; _messageColor = Colors.red; }); return; } setState(() { _isCheckingDuplicate = false; _duplicateCheckMessage = '사용 가능한 회사명입니다'; _messageColor = Colors.green; }); // 주소 업데이트 _controller.updateCompanyAddress( Address.fromFullAddress(_addressController.text) ); // 전화번호는 이미 포맷팅되어 있으므로 그대로 사용 _controller.contactPhoneController.text = _phoneNumberController.text; // 로딩 표시 showShadDialog( context: context, barrierDismissible: false, builder: (context) => ShadDialog( child: Container( padding: const EdgeInsets.all(20), child: const Row( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(), SizedBox(width: 20), Text('저장 중...'), ], ), ), ), ); try { final success = await _controller.saveCompany(); if (mounted) { Navigator.pop(context); // 로딩 다이얼로그 닫기 if (success) { ShadToaster.of(context).show( ShadToast( title: const Text('성공'), description: Text(companyId != null ? '회사 정보가 수정되었습니다.' : '회사가 등록되었습니다.'), ), ); Navigator.pop(context, true); } else { ShadToaster.of(context).show( ShadToast.destructive( title: const Text('오류'), description: const Text('회사 저장에 실패했습니다.'), ), ); } } } catch (e) { if (mounted) { Navigator.pop(context); // 로딩 다이얼로그 닫기 // 409 Conflict 처리 final errorMessage = e.toString(); if (errorMessage.contains('CONFLICT:')) { final conflictMessage = errorMessage.replaceFirst('Exception: CONFLICT: ', ''); setState(() { _duplicateCheckMessage = '❌ $conflictMessage'; _messageColor = Colors.red; }); ShadToaster.of(context).show( ShadToast.destructive( title: const Text('중복 오류'), description: Text(conflictMessage), ), ); } else { ShadToaster.of(context).show( ShadToast.destructive( title: const Text('오류'), description: Text('오류가 발생했습니다: $e'), ), ); } } } } @override Widget build(BuildContext context) { final isEditMode = companyId != null; final title = isEditMode ? '회사 정보 수정' : '회사 등록'; return Scaffold( appBar: AppBar(title: Text(title)), body: Padding( padding: const EdgeInsets.all(16.0), child: Form( key: _controller.formKey, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 회사 유형 선택 FormFieldWrapper( label: "회사 유형", child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ ShadCheckbox( value: _controller.selectedCompanyTypes.contains(CompanyType.customer), onChanged: (checked) { setState(() { _controller.toggleCompanyType(CompanyType.customer, checked); }); }, ), const SizedBox(width: 8), const Text('고객사'), ], ), const SizedBox(height: 8), Row( children: [ ShadCheckbox( value: _controller.selectedCompanyTypes.contains(CompanyType.partner), onChanged: (checked) { setState(() { _controller.toggleCompanyType(CompanyType.partner, checked); }); }, ), const SizedBox(width: 8), const Text('파트너사'), ], ), ], ), ), const SizedBox(height: 16), // 부모 회사 선택 (StandardDropdown 사용) FormFieldWrapper( label: "부모 회사", child: StandardIntDropdown?>( label: '', // FormFieldWrapper에서 이미 라벨 표시 isRequired: false, items: [ null, // '없음 (본사)' 옵션 ..._controller.availableParentCompanies.entries, ], isLoading: false, // 부모회사 로딩 상태 필요시 추가 selectedValue: _controller.selectedParentCompanyId != null ? _controller.availableParentCompanies.entries .where((entry) => entry.key == _controller.selectedParentCompanyId) .firstOrNull : null, onChanged: (MapEntry? selectedCompany) { debugPrint('🔄 부모 회사 선택: ${selectedCompany?.key}'); setState(() { _controller.selectedParentCompanyId = selectedCompany?.key; }); debugPrint('✅ 부모 회사 설정 완료: ${_controller.selectedParentCompanyId}'); }, itemBuilder: (MapEntry? company) => company == null ? const Text('없음 (본사)') : Text(company.value), selectedItemBuilder: (MapEntry? company) => company == null ? const Text('없음 (본사)') : Text(company.value), idExtractor: (MapEntry? company) => company?.key ?? -1, placeholder: '부모 회사를 선택하세요 (선택사항)', ), ), const SizedBox(height: 16), // 회사명 (필수) FormFieldWrapper( label: "회사명 *", child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: ShadInputFormField( controller: _controller.nameController, placeholder: const Text('회사명을 입력하세요 (저장 시 중복 검사)'), validator: (value) { if (value.trim().isEmpty) { return '회사명을 입력하세요'; } if (value.trim().length < 2) { return '회사명은 2자 이상 입력하세요'; } return null; }, ), ), ], ), // 중복 검사 메시지 영역 (고정 높이) SizedBox( height: 24, child: Padding( padding: const EdgeInsets.only(top: 4), child: Text( _duplicateCheckMessage, style: TextStyle( fontSize: 12, color: _messageColor, ), ), ), ), ], ), ), const SizedBox(height: 16), // 우편번호 검색 FormFieldWrapper( label: "우편번호", child: Row( children: [ Expanded( child: ShadInputFormField( controller: _controller.zipcodeController, placeholder: const Text('우편번호'), readOnly: true, ), ), const SizedBox(width: 8), ShadButton( onPressed: () async { // 우편번호 검색 다이얼로그 호출 final result = await _showZipcodeSearchDialog(); if (result != null) { _controller.selectZipcode(result); // 주소 필드도 업데이트 _addressController.text = '${result.sido} ${result.gu} ${result.etc ?? ''}'.trim(); } }, child: const Text('검색'), ), ], ), ), const SizedBox(height: 16), // 주소 (선택) FormFieldWrapper( label: "주소", child: ShadInputFormField( controller: _addressController, placeholder: const Text('상세 주소를 입력하세요'), maxLines: 2, ), ), const SizedBox(height: 16), // 담당자명 (필수) FormFieldWrapper( label: "담당자명 *", child: ShadInputFormField( controller: _controller.contactNameController, placeholder: const Text('담당자명을 입력하세요'), validator: (value) { if (value.trim().isEmpty) { return '담당자명을 입력하세요'; } return null; }, ), ), const SizedBox(height: 16), // 담당자 직급 (선택) FormFieldWrapper( label: "담당자 직급", child: ShadInputFormField( controller: _controller.contactPositionController, placeholder: const Text('담당자 직급을 입력하세요'), ), ), const SizedBox(height: 16), // 담당자 연락처 (필수) - 사용자 폼과 동일한 패턴 FormFieldWrapper( label: "담당자 연락처 *", child: ShadInputFormField( controller: _phoneNumberController, placeholder: const Text('010-1234-5678'), keyboardType: TextInputType.phone, inputFormatters: [ KoreanPhoneFormatter(), // 한국식 전화번호 자동 포맷팅 ], validator: PhoneValidator.validate, // 전화번호 유효성 검증 ), ), const SizedBox(height: 16), // 담당자 이메일 (필수) FormFieldWrapper( label: "담당자 이메일 *", child: ShadInputFormField( controller: _controller.contactEmailController, placeholder: const Text('example@company.com'), keyboardType: TextInputType.emailAddress, validator: (value) { if (value.trim().isEmpty) { return '담당자 이메일을 입력하세요'; } return validateEmail(value); }, ), ), const SizedBox(height: 16), // 비고 (선택) FormFieldWrapper( label: "비고", child: ShadInputFormField( controller: _controller.remarkController, placeholder: const Text('추가 정보나 메모를 입력하세요'), maxLines: 3, ), ), const SizedBox(height: 32), // 저장 버튼 ShadButton( onPressed: _isCheckingDuplicate ? null : _saveCompany, size: ShadButtonSize.lg, width: double.infinity, child: Text( isEditMode ? '수정 완료' : '등록 완료', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), const SizedBox(height: 16), ], ), ), ), ), ); } }