import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'dart:async'; import 'package:superport/models/company_model.dart'; import 'package:superport/screens/common/theme_shadcn.dart'; import 'package:superport/screens/common/components/shadcn_components.dart'; import 'package:superport/services/mock_data_service.dart'; import 'package:superport/screens/company/widgets/company_branch_dialog.dart'; import 'package:superport/screens/company/controllers/company_list_controller.dart'; /// shadcn/ui 스타일로 재설계된 회사 관리 화면 class CompanyListRedesign extends StatefulWidget { const CompanyListRedesign({super.key}); @override State createState() => _CompanyListRedesignState(); } class _CompanyListRedesignState extends State { late CompanyListController _controller; final ScrollController _scrollController = ScrollController(); final TextEditingController _searchController = TextEditingController(); Timer? _debounceTimer; @override void initState() { super.initState(); _controller = CompanyListController(dataService: MockDataService()); _controller.initialize(); _setupScrollListener(); } @override void dispose() { _controller.dispose(); _scrollController.dispose(); _searchController.dispose(); _debounceTimer?.cancel(); super.dispose(); } /// 스크롤 리스너 설정 (무한 스크롤) void _setupScrollListener() { _scrollController.addListener(() { if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) { _controller.loadMore(); } }); } /// 검색어 입력 처리 (디바운싱) void _onSearchChanged(String value) { _debounceTimer?.cancel(); _debounceTimer = Timer(const Duration(milliseconds: 500), () { _controller.updateSearchKeyword(value); }); } /// 회사 추가 화면으로 이동 void _navigateToAddScreen() async { final result = await Navigator.pushNamed(context, '/company/add'); if (result == true) { _controller.refresh(); } } /// 회사 삭제 처리 void _deleteCompany(int id) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('삭제 확인'), content: const Text('이 회사 정보를 삭제하시겠습니까?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('취소'), ), TextButton( onPressed: () async { Navigator.pop(context); final success = await _controller.deleteCompany(id); if (!success && mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(_controller.error ?? '삭제에 실패했습니다'), backgroundColor: Colors.red, ), ); } }, child: const Text('삭제'), ), ], ), ); } /// 지점 다이얼로그 표시 void _showBranchDialog(Company mainCompany) { showDialog( context: context, builder: (context) => CompanyBranchDialog(mainCompany: mainCompany), ); } /// Branch 객체를 Company 객체로 변환 Company _convertBranchToCompany(Branch branch) { return Company( id: branch.id, name: branch.name, address: branch.address, contactName: branch.contactName, contactPosition: branch.contactPosition, contactPhone: branch.contactPhone, contactEmail: branch.contactEmail, companyTypes: [], remark: branch.remark, ); } /// 회사 유형 배지 생성 Widget _buildCompanyTypeChips(List types) { return Wrap( spacing: ShadcnTheme.spacing1, children: types.map((type) { return ShadcnBadge( text: companyTypeToString(type), variant: type == CompanyType.customer ? ShadcnBadgeVariant.primary : ShadcnBadgeVariant.secondary, size: ShadcnBadgeSize.small, ); }).toList(), ); } /// 본사/지점 구분 배지 생성 Widget _buildCompanyTypeLabel(bool isBranch) { return ShadcnBadge( text: isBranch ? '지점' : '본사', variant: isBranch ? ShadcnBadgeVariant.outline : ShadcnBadgeVariant.primary, size: ShadcnBadgeSize.small, ); } /// 회사 이름 표시 (지점인 경우 본사명 포함) Widget _buildCompanyNameText( Company company, bool isBranch, { String? mainCompanyName, }) { if (isBranch && mainCompanyName != null) { return Text.rich( TextSpan( children: [ TextSpan(text: '$mainCompanyName > ', style: ShadcnTheme.bodyMuted), TextSpan(text: company.name, style: ShadcnTheme.bodyMedium), ], ), ); } else { return Text(company.name, style: ShadcnTheme.bodyMedium); } } @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: _controller, child: Consumer( builder: (context, controller, child) { // 본사와 지점 구분하기 위한 데이터 준비 final List> displayCompanies = []; for (final company in controller.filteredCompanies) { displayCompanies.add({ 'company': company, 'isBranch': false, 'mainCompanyName': null, }); if (company.branches != null) { for (final branch in company.branches!) { displayCompanies.add({ 'branch': branch, 'companyId': company.id, 'isBranch': true, 'mainCompanyName': company.name, }); } } } final int totalCount = displayCompanies.length; return SingleChildScrollView( controller: _scrollController, padding: const EdgeInsets.all(ShadcnTheme.spacing6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 헤더 및 검색 바 Row( children: [ Expanded( child: Container( height: 40, decoration: BoxDecoration( color: ShadcnTheme.card, borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), border: Border.all(color: ShadcnTheme.border), ), child: TextField( controller: _searchController, onChanged: _onSearchChanged, decoration: InputDecoration( hintText: '회사명, 담당자명, 연락처로 검색', hintStyle: TextStyle(color: ShadcnTheme.muted), prefixIcon: Icon(Icons.search, color: ShadcnTheme.muted), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), ), ), ), const SizedBox(width: ShadcnTheme.spacing4), ShadcnButton( text: '회사 추가', onPressed: _navigateToAddScreen, variant: ShadcnButtonVariant.primary, textColor: Colors.white, icon: Icon(Icons.add), ), ], ), const SizedBox(height: ShadcnTheme.spacing4), // 결과 정보 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('총 $totalCount개 회사', style: ShadcnTheme.bodyMuted), if (controller.searchKeyword.isNotEmpty) Text( '"${controller.searchKeyword}" 검색 결과', style: ShadcnTheme.bodyMuted, ), ], ), const SizedBox(height: ShadcnTheme.spacing4), // 에러 메시지 if (controller.error != null) Container( padding: const EdgeInsets.all(ShadcnTheme.spacing4), margin: const EdgeInsets.only(bottom: ShadcnTheme.spacing4), decoration: BoxDecoration( color: Colors.red.shade50, borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), border: Border.all(color: Colors.red.shade200), ), child: Row( children: [ Icon(Icons.error_outline, color: Colors.red), const SizedBox(width: ShadcnTheme.spacing2), Expanded( child: Text( controller.error!, style: TextStyle(color: Colors.red.shade700), ), ), IconButton( icon: Icon(Icons.close, size: 16), onPressed: controller.clearError, padding: EdgeInsets.zero, constraints: BoxConstraints(maxHeight: 24, maxWidth: 24), ), ], ), ), // 테이블 카드 Container( width: double.infinity, decoration: BoxDecoration( color: ShadcnTheme.card, borderRadius: BorderRadius.circular(ShadcnTheme.radiusLg), border: Border.all(color: ShadcnTheme.border), boxShadow: ShadcnTheme.cardShadow, ), child: controller.isLoading && controller.companies.isEmpty ? Container( padding: const EdgeInsets.all(ShadcnTheme.spacing8), child: Center( child: CircularProgressIndicator(), ), ) : displayCompanies.isEmpty ? Container( padding: const EdgeInsets.all(ShadcnTheme.spacing8), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.business_outlined, size: 48, color: ShadcnTheme.muted, ), const SizedBox(height: ShadcnTheme.spacing4), Text( controller.searchKeyword.isNotEmpty ? '검색 결과가 없습니다' : '등록된 회사가 없습니다', style: ShadcnTheme.bodyMuted, ), ], ), ), ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 테이블 헤더 Container( padding: const EdgeInsets.symmetric( horizontal: ShadcnTheme.spacing4, vertical: ShadcnTheme.spacing3, ), decoration: BoxDecoration( color: ShadcnTheme.muted.withValues(alpha: 0.3), border: Border( bottom: BorderSide(color: ShadcnTheme.border), ), ), child: Row( children: [ Expanded( flex: 1, child: Text( '번호', style: ShadcnTheme.bodyMedium, ), ), Expanded( flex: 3, child: Text( '회사명', style: ShadcnTheme.bodyMedium, ), ), Expanded( flex: 2, child: Text( '구분', style: ShadcnTheme.bodyMedium, ), ), Expanded( flex: 2, child: Text( '유형', style: ShadcnTheme.bodyMedium, ), ), Expanded( flex: 2, child: Text( '연락처', style: ShadcnTheme.bodyMedium, ), ), Expanded( flex: 2, child: Text( '관리', style: ShadcnTheme.bodyMedium, ), ), ], ), ), // 테이블 데이터 ...displayCompanies.asMap().entries.map((entry) { final int index = entry.key; final companyData = entry.value; final bool isBranch = companyData['isBranch'] as bool; final Company company = isBranch ? _convertBranchToCompany(companyData['branch'] as Branch) : companyData['company'] as Company; final String? mainCompanyName = companyData['mainCompanyName'] as String?; return Container( padding: const EdgeInsets.symmetric( horizontal: ShadcnTheme.spacing4, vertical: ShadcnTheme.spacing3, ), decoration: BoxDecoration( border: Border( bottom: BorderSide(color: ShadcnTheme.border), ), ), child: Row( children: [ // 번호 Expanded( flex: 1, child: Text( '${index + 1}', style: ShadcnTheme.bodySmall, ), ), // 회사명 Expanded( flex: 3, child: _buildCompanyNameText( company, isBranch, mainCompanyName: mainCompanyName, ), ), // 구분 Expanded( flex: 2, child: _buildCompanyTypeLabel(isBranch), ), // 유형 Expanded( flex: 2, child: _buildCompanyTypeChips( company.companyTypes, ), ), // 연락처 Expanded( flex: 2, child: Text( company.contactPhone ?? '-', style: ShadcnTheme.bodySmall, ), ), // 관리 Expanded( flex: 2, child: Row( mainAxisSize: MainAxisSize.min, children: [ if (!isBranch && company.branches != null && company.branches!.isNotEmpty) ShadcnButton( text: '지점보기', onPressed: () => _showBranchDialog(company), variant: ShadcnButtonVariant.secondary, size: ShadcnButtonSize.small, ), if (!isBranch && company.branches != null && company.branches!.isNotEmpty) const SizedBox( width: ShadcnTheme.spacing2, ), ShadcnButton( text: '수정', onPressed: company.id != null ? () { if (isBranch) { Navigator.pushNamed( context, '/company/edit', arguments: { 'companyId': companyData['companyId'], 'isBranch': true, 'mainCompanyName': mainCompanyName, 'branchId': company.id, }, ).then((result) { if (result == true) controller.refresh(); }); } else { Navigator.pushNamed( context, '/company/edit', arguments: { 'companyId': company.id, 'isBranch': false, }, ).then((result) { if (result == true) controller.refresh(); }); } } : null, variant: ShadcnButtonVariant.secondary, size: ShadcnButtonSize.small, ), const SizedBox( width: ShadcnTheme.spacing2, ), ShadcnButton( text: '삭제', onPressed: (!isBranch && company.id != null) ? () => _deleteCompany(company.id!) : null, variant: ShadcnButtonVariant.destructive, size: ShadcnButtonSize.small, ), ], ), ), ], ), ); }), ], ), ), // 무한 스크롤 로딩 인디케이터 if (controller.isLoading && controller.companies.isNotEmpty) Container( padding: const EdgeInsets.all(ShadcnTheme.spacing4), child: Center( child: CircularProgressIndicator(), ), ), // 더 이상 로드할 데이터가 없을 때 메시지 if (!controller.hasMore && controller.companies.isNotEmpty) Container( padding: const EdgeInsets.all(ShadcnTheme.spacing4), child: Center( child: Text( '모든 회사를 불러왔습니다', style: ShadcnTheme.bodyMuted, ), ), ), ], ), ); }, ), ); } }