refactor: UI 화면 통합 및 불필요한 파일 정리
Some checks failed
Flutter Test & Quality Check / Build APK (push) Has been cancelled
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled

- 모든 *_redesign.dart 파일을 기본 화면 파일로 통합
- 백업용 컨트롤러 파일들 제거 (*_controller.backup.dart)
- 사용하지 않는 예제 및 테스트 파일 제거
- Clean Architecture 적용 후 남은 정리 작업 완료
- 테스트 코드 정리 및 구조 개선 준비

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-08-11 14:00:44 +09:00
parent 162fe08618
commit 1e6da44917
103 changed files with 1224 additions and 2976 deletions

View File

@@ -16,7 +16,7 @@ import 'package:flutter/material.dart';
// import 'package:superport/models/address_model.dart'; // 사용되지 않는 import
import 'package:superport/models/company_model.dart';
// import 'package:superport/screens/common/custom_widgets.dart'; // 사용되지 않는 import
import 'package:superport/screens/common/theme_tailwind.dart';
import 'package:superport/screens/common/theme_shadcn.dart';
import 'package:superport/screens/company/controllers/company_form_controller.dart';
// import 'package:superport/screens/company/widgets/branch_card.dart'; // 사용되지 않는 import
import 'package:superport/screens/company/widgets/company_form_header.dart';
@@ -48,7 +48,7 @@ class CompanyTypeSelector extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('회사 유형', style: AppThemeTailwind.formLabelStyle),
Text('회사 유형', style: ShadcnTheme.labelMedium),
const SizedBox(height: 8),
Row(
children: [
@@ -357,7 +357,7 @@ class _CompanyFormScreenState extends State<CompanyFormScreen> {
child: ElevatedButton(
onPressed: _saveCompany,
style: ElevatedButton.styleFrom(
backgroundColor: AppThemeTailwind.primary,
backgroundColor: ShadcnTheme.primary,
minimumSize: const Size.fromHeight(48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
@@ -463,7 +463,7 @@ class _CompanyFormScreenState extends State<CompanyFormScreen> {
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
child: Text(
'지점 정보',
style: AppThemeTailwind.subheadingStyle,
style: ShadcnTheme.headingH6,
),
),
if (_controller.branchControllers.isNotEmpty)
@@ -507,7 +507,7 @@ class _CompanyFormScreenState extends State<CompanyFormScreen> {
child: ElevatedButton(
onPressed: _saveCompany,
style: ElevatedButton.styleFrom(
backgroundColor: AppThemeTailwind.primary,
backgroundColor: ShadcnTheme.primary,
minimumSize: const Size.fromHeight(48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),

View File

@@ -17,25 +17,23 @@ import 'package:superport/screens/company/widgets/company_branch_dialog.dart';
import 'package:superport/screens/company/controllers/company_list_controller.dart';
/// shadcn/ui ( UI )
class CompanyListRedesign extends StatefulWidget {
const CompanyListRedesign({super.key});
class CompanyList extends StatefulWidget {
const CompanyList({super.key});
@override
State<CompanyListRedesign> createState() => _CompanyListRedesignState();
State<CompanyList> createState() => _CompanyListState();
}
class _CompanyListRedesignState extends State<CompanyListRedesign> {
class _CompanyListState extends State<CompanyList> {
late CompanyListController _controller;
final TextEditingController _searchController = TextEditingController();
Timer? _debounceTimer;
int _currentPage = 1;
final int _pageSize = 10;
@override
void initState() {
super.initState();
_controller = CompanyListController();
_controller.initializeWithPageSize(_pageSize);
_controller.initializeWithPageSize(10); //
}
@override
@@ -50,10 +48,7 @@ class _CompanyListRedesignState extends State<CompanyListRedesign> {
void _onSearchChanged(String value) {
_debounceTimer?.cancel();
_debounceTimer = Timer(AppConstants.searchDebounce, () {
setState(() {
_currentPage = 1;
});
_controller.search(value);
_controller.search(value); // Controller가
});
}
@@ -228,37 +223,15 @@ class _CompanyListRedesignState extends State<CompanyListRedesign> {
}
}
final int totalCount = displayCompanies.length;
//
final int startIndex = (_currentPage - 1) * _pageSize;
final int endIndex = startIndex + _pageSize;
// Controller가
final List<Map<String, dynamic>> pagedCompanies = displayCompanies;
final int totalCount = controller.total; //
//
print('🔍 [VIEW DEBUG] 화면 페이지네이션 상태');
print('filteredCompanies 수: ${controller.filteredCompanies.length}');
print('displayCompanies 수: ${displayCompanies.length}개 (지점 포함)');
print('현재 페이지: $_currentPage');
print(' • 페이지 크기: $_pageSize');
print(' • startIndex: $startIndex, endIndex: $endIndex');
// startIndex가 displayCompanies.length보다
if (startIndex >= displayCompanies.length && displayCompanies.isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_currentPage = 1;
});
});
}
final List<Map<String, dynamic>> pagedCompanies = displayCompanies.isEmpty
? []
: displayCompanies.sublist(
startIndex.clamp(0, displayCompanies.length),
endIndex.clamp(0, displayCompanies.length),
);
print(' • 화면에 표시될 항목 수: ${pagedCompanies.length}');
print('🔍 [VIEW DEBUG] 페이지네이션 상태');
print(' • Controller items: ${controller.companies.length}');
print('전체 개수: ${controller.total}');
print('현재 페이지: ${controller.currentPage}');
print(' • 페이지 크기: ${controller.pageSize}');
//
if (controller.isLoading && controller.companies.isEmpty) {
@@ -344,7 +317,7 @@ class _CompanyListRedesignState extends State<CompanyListRedesign> {
],
rows: [
...pagedCompanies.asMap().entries.map((entry) {
final int index = startIndex + entry.key;
final int index = ((controller.currentPage - 1) * controller.pageSize) + entry.key;
final companyData = entry.value;
final bool isBranch = companyData['isBranch'] as bool;
final Company company =
@@ -457,15 +430,18 @@ class _CompanyListRedesignState extends State<CompanyListRedesign> {
],
),
// ( )
// (Controller )
pagination: Pagination(
totalCount: totalCount,
currentPage: _currentPage,
pageSize: _pageSize,
totalCount: controller.total,
currentPage: controller.currentPage,
pageSize: controller.pageSize,
onPageChanged: (page) {
setState(() {
_currentPage = page;
});
//
if (page > controller.currentPage) {
controller.loadNextPage();
} else if (page == 1) {
controller.refresh();
}
},
),
);
@@ -473,4 +449,4 @@ class _CompanyListRedesignState extends State<CompanyListRedesign> {
),
);
}
}
}

View File

@@ -96,8 +96,9 @@ class CompanyFormController {
try {
List<Company> companies;
// API만 사용
companies = await _companyService.getCompanies();
// API만 사용 (PaginatedResponse에서 items 추출)
final response = await _companyService.getCompanies();
companies = response.items;
companyNames = companies.map((c) => c.name).toList();
filteredCompanyNames = companyNames;
@@ -347,9 +348,9 @@ class CompanyFormController {
if (_useApi) {
try {
// 회사명 목록을 조회하여 중복 확인
final companies = await _companyService.getCompanies(search: name);
final response = await _companyService.getCompanies(search: name);
// 정확히 일치하는 회사명이 있는지 확인
for (final company in companies) {
for (final company in response.items) {
if (company.name.toLowerCase() == name.toLowerCase()) {
return company;
}

View File

@@ -1,331 +0,0 @@
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/core/errors/failures.dart';
// 회사 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
class CompanyListController extends ChangeNotifier {
final CompanyService _companyService = GetIt.instance<CompanyService>();
List<Company> companies = [];
List<Company> filteredCompanies = [];
String searchKeyword = '';
final Set<int> selectedCompanyIds = {};
bool _isLoading = false;
String? _error;
// API만 사용
// 페이지네이션
int _currentPage = 1;
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();
// 초기 데이터 로드
Future<void> initialize() async {
print('╔══════════════════════════════════════════════════════════');
print('║ 🚀 회사 목록 초기화 시작');
print('║ • 페이지 크기: $_perPage개');
print('╚══════════════════════════════════════════════════════════');
await loadData(isRefresh: true);
}
// 페이지 크기를 지정하여 초기화
Future<void> initializeWithPageSize(int pageSize) async {
_perPage = pageSize;
print('╔══════════════════════════════════════════════════════════');
print('║ 🚀 회사 목록 초기화 시작 (커스텀 페이지 크기)');
print('║ • 페이지 크기: $_perPage개');
print('╚══════════════════════════════════════════════════════════');
await loadData(isRefresh: true);
}
// 데이터 로드 및 필터 적용
Future<void> loadData({bool isRefresh = false}) async {
print('🔍 [DEBUG] loadData 시작 - currentPage: $_currentPage, hasMore: $_hasMore, companies.length: ${companies.length}');
print('[CompanyListController] loadData called - isRefresh: $isRefresh');
if (isRefresh) {
_currentPage = 1;
_hasMore = true;
companies.clear();
filteredCompanies.clear();
}
if (_isLoading || (!_hasMore && !isRefresh)) return;
_isLoading = true;
_error = null;
notifyListeners();
try {
// API 호출 - 지점 정보 포함
print('[CompanyListController] Using API to fetch companies with branches');
// 지점 정보를 포함한 전체 회사 목록 가져오기
final apiCompaniesWithBranches = await _companyService.getCompaniesWithBranchesFlat();
// 상세한 회사 정보 로그 출력
print('╔══════════════════════════════════════════════════════════');
print('║ 📊 회사 목록 로드 완료');
print('║ ▶ 총 회사 수: ${apiCompaniesWithBranches.length}');
print('╟──────────────────────────────────────────────────────────');
// 지점이 있는 회사와 없는 회사 구분
int companiesWithBranches = 0;
int totalBranches = 0;
for (final company in apiCompaniesWithBranches) {
if (company.branches?.isNotEmpty ?? false) {
companiesWithBranches++;
totalBranches += company.branches!.length;
print('║ • ${company.name}: ${company.branches!.length}개 지점');
}
}
final companiesWithoutBranches = apiCompaniesWithBranches.length - companiesWithBranches;
print('╟──────────────────────────────────────────────────────────');
print('║ 📈 통계');
print('║ • 지점이 있는 회사: ${companiesWithBranches}');
print('║ • 지점이 없는 회사: ${companiesWithoutBranches}');
print('║ • 총 지점 수: ${totalBranches}');
print('╚══════════════════════════════════════════════════════════');
// 검색어 필터 적용 (서버에서 필터링이 안 되므로 클라이언트에서 처리)
List<Company> filteredApiCompanies = apiCompaniesWithBranches;
if (searchKeyword.isNotEmpty) {
final keyword = searchKeyword.toLowerCase();
filteredApiCompanies = apiCompaniesWithBranches.where((company) {
return company.name.toLowerCase().contains(keyword) ||
(company.contactName?.toLowerCase().contains(keyword) ?? false) ||
(company.contactPhone?.toLowerCase().contains(keyword) ?? false);
}).toList();
print('╔══════════════════════════════════════════════════════════');
print('║ 🔍 검색 필터 적용');
print('║ • 검색어: "$searchKeyword"');
print('║ • 필터 전: ${apiCompaniesWithBranches.length}');
print('║ • 필터 후: ${filteredApiCompanies.length}');
print('╚══════════════════════════════════════════════════════════');
}
// 활성 상태 필터 적용 (현재 API에서 지원하지 않으므로 주석 처리)
// if (_isActiveFilter != null) {
// filteredApiCompanies = filteredApiCompanies.where((c) => c.isActive == _isActiveFilter).toList();
// }
// 전체 데이터를 한 번에 로드 (View에서 페이지네이션 처리)
companies = filteredApiCompanies;
_hasMore = false; // 전체 데이터를 로드했으므로 더 이상 로드할 필요 없음
print('╔══════════════════════════════════════════════════════════');
print('║ 📑 전체 데이터 로드 완료');
print('║ • 로드된 회사 수: ${companies.length}');
print('║ • 필터링된 회사 수: ${filteredApiCompanies.length}');
print('║ • View에서 페이지네이션 처리 예정');
print('╚══════════════════════════════════════════════════════════');
// 필터 적용
applyFilters();
print('╔══════════════════════════════════════════════════════════');
print('║ ✅ 최종 화면 표시');
print('║ • 화면에 표시될 회사 수: ${filteredCompanies.length}');
print('╚══════════════════════════════════════════════════════════');
selectedCompanyIds.clear();
} on Failure catch (e) {
print('[CompanyListController] Failure loading companies: ${e.message}');
_error = e.message;
} catch (e, stackTrace) {
print('[CompanyListController] Error loading companies: $e');
print('[CompanyListController] Error type: ${e.runtimeType}');
print('[CompanyListController] Stack trace: $stackTrace');
_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에서 지원안함)
// if (_isActiveFilter != null) {
// 추후 API 지원 시 구현
// }
return true;
}).toList();
}
// 검색어 변경
Future<void> updateSearchKeyword(String keyword) async {
searchKeyword = keyword;
if (keyword.isNotEmpty) {
print('╔══════════════════════════════════════════════════════════');
print('║ 🔍 검색어 변경: "$keyword"');
print('╚══════════════════════════════════════════════════════════');
} else {
print('╔══════════════════════════════════════════════════════════');
print('║ ❌ 검색어 초기화');
print('╚══════════════════════════════════════════════════════════');
}
// API 사용 시 새로 조회
await loadData(isRefresh: true);
}
// 활성 상태 필터 변경
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 {
// API를 통한 삭제
await _companyService.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 {
print('🔍 [DEBUG] loadMore 호출됨 - hasMore: $_hasMore, isLoading: $_isLoading');
if (!_hasMore || _isLoading) {
print('🔍 [DEBUG] loadMore 조건 미충족으로 종료 (hasMore: $_hasMore, isLoading: $_isLoading)');
return;
}
print('🔍 [DEBUG] loadMore 실행 - 추가 데이터 로드 시작');
await loadData();
}
// API만 사용하므로 토글 기능 제거
// 에러 처리
void clearError() {
_error = null;
notifyListeners();
}
// 리프레시
Future<void> refresh() async {
print('╔══════════════════════════════════════════════════════════');
print('║ 🔄 회사 목록 새로고침 시작');
print('╚══════════════════════════════════════════════════════════');
await loadData(isRefresh: true);
}
@override
void dispose() {
super.dispose();
}
}

View File

@@ -48,8 +48,8 @@ class CompanyListController extends BaseListController<Company> {
required PaginationParams params,
Map<String, dynamic>? additionalFilters,
}) async {
// API 호출 - 회사 목록 조회
final apiCompanies = await ErrorHandler.handleApiCall<List<Company>>(
// API 호출 - 회사 목록 조회 (이제 PaginatedResponse 반환)
final response = await ErrorHandler.handleApiCall<dynamic>(
() => _companyService.getCompanies(
page: params.page,
perPage: params.perPage,
@@ -61,21 +61,17 @@ class CompanyListController extends BaseListController<Company> {
},
);
final items = apiCompanies ?? [];
// 임시로 메타데이터 생성 (추후 API에서 실제 메타데이터 반환 시 수정)
// PaginatedResponse를 PagedResult로 변환
final meta = PaginationMeta(
currentPage: params.page,
perPage: params.perPage,
total: items.length < params.perPage ?
(params.page - 1) * params.perPage + items.length :
params.page * params.perPage + 1,
totalPages: items.length < params.perPage ? params.page : params.page + 1,
hasNext: items.length >= params.perPage,
hasPrevious: params.page > 1,
currentPage: response.page,
perPage: response.size,
total: response.totalElements,
totalPages: response.totalPages,
hasNext: !response.last,
hasPrevious: !response.first,
);
return PagedResult(items: items, meta: meta);
return PagedResult(items: response.items, meta: meta);
}
@override

View File

@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
import 'package:superport/models/address_model.dart';
import 'package:superport/models/company_model.dart';
import 'package:superport/screens/common/custom_widgets.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
import 'package:superport/screens/common/theme_shadcn.dart';
import 'package:superport/screens/common/widgets/address_input.dart';
import 'package:superport/screens/company/widgets/contact_info_widget.dart';
import 'package:superport/utils/validators.dart';
@@ -81,7 +81,7 @@ class _BranchCardState extends State<BranchCard> {
children: [
Text(
'지점 #${widget.index + 1}',
style: AppThemeTailwind.subheadingStyle,
style: ShadcnTheme.headingH6,
),
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:superport/models/address_model.dart';
import 'package:superport/screens/common/custom_widgets.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
import 'package:superport/screens/common/theme_shadcn.dart';
import 'package:superport/screens/common/widgets/address_input.dart';
import 'package:superport/utils/validators.dart';
import 'package:superport/screens/company/widgets/company_name_autocomplete.dart';

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
import 'package:superport/screens/common/theme_shadcn.dart';
/// 주소에 대한 지도 대화상자를 표시합니다.
class MapDialog extends StatelessWidget {
@@ -68,7 +68,7 @@ class MapDialog extends StatelessWidget {
Icon(
Icons.map,
size: 64,
color: AppThemeTailwind.primary,
color: ShadcnTheme.primary,
),
const SizedBox(height: 16),
Text(