fix: API 응답 파싱 오류 수정 및 에러 처리 개선
주요 변경사항: - 창고 관리 API 응답 구조와 DTO 불일치 수정 - WarehouseLocationDto에 code, manager_phone 필드 추가 - RemoteDataSource에서 API 응답을 DTO 구조에 맞게 변환 - 회사 관리 API 응답 파싱 오류 수정 - CompanyResponse의 필수 필드를 nullable로 변경 - PaginatedResponse 구조 매핑 로직 개선 - 에러 처리 및 로깅 개선 - Service Layer에 상세 에러 로깅 추가 - Controller에서 에러 타입별 처리 - 새로운 유틸리티 추가 - ResponseInterceptor: API 응답 정규화 - DebugLogger: 디버깅 도구 - HealthCheckService: 서버 상태 확인 - 문서화 - API 통합 테스트 가이드 - 에러 분석 보고서 - 리팩토링 계획서
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:get_it/get_it.dart';
|
||||
import 'package:superport/models/warehouse_location_model.dart';
|
||||
import 'package:superport/services/warehouse_service.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
|
||||
/// 입고지 리스트 상태 및 CRUD만 담당하는 컨트롤러 클래스 (SRP 적용)
|
||||
/// UI, 네비게이션, 다이얼로그 등은 포함하지 않음
|
||||
@@ -45,6 +46,8 @@ class WarehouseLocationListController extends ChangeNotifier {
|
||||
Future<void> loadWarehouseLocations({bool isInitialLoad = true}) async {
|
||||
if (_isLoading) return;
|
||||
|
||||
print('[WarehouseLocationListController] loadWarehouseLocations started - isInitialLoad: $isInitialLoad');
|
||||
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
|
||||
@@ -59,12 +62,15 @@ class WarehouseLocationListController extends ChangeNotifier {
|
||||
try {
|
||||
if (useApi && GetIt.instance.isRegistered<WarehouseService>()) {
|
||||
// API 사용
|
||||
print('[WarehouseLocationListController] Using API to fetch warehouse locations');
|
||||
final fetchedLocations = await _warehouseService.getWarehouseLocations(
|
||||
page: _currentPage,
|
||||
perPage: _pageSize,
|
||||
isActive: _isActive,
|
||||
);
|
||||
|
||||
print('[WarehouseLocationListController] API returned ${fetchedLocations.length} locations');
|
||||
|
||||
if (isInitialLoad) {
|
||||
_warehouseLocations = fetchedLocations;
|
||||
} else {
|
||||
@@ -77,9 +83,12 @@ class WarehouseLocationListController extends ChangeNotifier {
|
||||
_total = await _warehouseService.getTotalWarehouseLocations(
|
||||
isActive: _isActive,
|
||||
);
|
||||
print('[WarehouseLocationListController] Total warehouse locations: $_total');
|
||||
} else {
|
||||
// Mock 데이터 사용
|
||||
print('[WarehouseLocationListController] Using Mock data');
|
||||
final allLocations = mockDataService?.getAllWarehouseLocations() ?? [];
|
||||
print('[WarehouseLocationListController] Mock data has ${allLocations.length} locations');
|
||||
|
||||
// 필터링 적용
|
||||
var filtered = allLocations;
|
||||
@@ -113,12 +122,21 @@ class WarehouseLocationListController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
_applySearchFilter();
|
||||
print('[WarehouseLocationListController] After filtering: ${_filteredLocations.length} locations shown');
|
||||
|
||||
if (!isInitialLoad) {
|
||||
_currentPage++;
|
||||
}
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
} catch (e, stackTrace) {
|
||||
print('[WarehouseLocationListController] Error loading warehouse locations: $e');
|
||||
print('[WarehouseLocationListController] Error type: ${e.runtimeType}');
|
||||
print('[WarehouseLocationListController] Stack trace: $stackTrace');
|
||||
|
||||
if (e is ServerFailure) {
|
||||
_error = e.message;
|
||||
} else {
|
||||
_error = '오류 발생: ${e.toString()}';
|
||||
}
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:superport/models/warehouse_location_model.dart';
|
||||
import 'package:superport/screens/common/theme_shadcn.dart';
|
||||
import 'package:superport/screens/common/components/shadcn_components.dart';
|
||||
@@ -16,23 +17,30 @@ class WarehouseLocationListRedesign extends StatefulWidget {
|
||||
|
||||
class _WarehouseLocationListRedesignState
|
||||
extends State<WarehouseLocationListRedesign> {
|
||||
final WarehouseLocationListController _controller =
|
||||
WarehouseLocationListController();
|
||||
late WarehouseLocationListController _controller;
|
||||
int _currentPage = 1;
|
||||
final int _pageSize = 10;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.loadWarehouseLocations();
|
||||
_controller = WarehouseLocationListController();
|
||||
// 초기 데이터 로드
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_controller.loadWarehouseLocations();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// 리스트 새로고침
|
||||
void _reload() {
|
||||
setState(() {
|
||||
_controller.loadWarehouseLocations();
|
||||
_currentPage = 1;
|
||||
});
|
||||
_currentPage = 1;
|
||||
_controller.loadWarehouseLocations();
|
||||
}
|
||||
|
||||
/// 입고지 추가 폼으로 이동
|
||||
@@ -72,11 +80,9 @@ class _WarehouseLocationListRedesignState
|
||||
child: const Text('취소'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_controller.deleteWarehouseLocation(id);
|
||||
});
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
await _controller.deleteWarehouseLocation(id);
|
||||
},
|
||||
child: const Text('삭제'),
|
||||
),
|
||||
@@ -87,17 +93,52 @@ class _WarehouseLocationListRedesignState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final int totalCount = _controller.warehouseLocations.length;
|
||||
final int startIndex = (_currentPage - 1) * _pageSize;
|
||||
final int endIndex =
|
||||
(startIndex + _pageSize) > totalCount
|
||||
? totalCount
|
||||
: (startIndex + _pageSize);
|
||||
final List<WarehouseLocation> pagedLocations = _controller
|
||||
.warehouseLocations
|
||||
.sublist(startIndex, endIndex);
|
||||
return ChangeNotifierProvider.value(
|
||||
value: _controller,
|
||||
child: Consumer<WarehouseLocationListController>(
|
||||
builder: (context, controller, child) {
|
||||
// 로딩 중일 때
|
||||
if (controller.isLoading && controller.warehouseLocations.isEmpty) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
// 에러가 있을 때
|
||||
if (controller.error != null) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'오류가 발생했습니다',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(controller.error!),
|
||||
SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: _reload,
|
||||
child: Text('다시 시도'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final int totalCount = controller.warehouseLocations.length;
|
||||
final int startIndex = (_currentPage - 1) * _pageSize;
|
||||
final int endIndex =
|
||||
(startIndex + _pageSize) > totalCount
|
||||
? totalCount
|
||||
: (startIndex + _pageSize);
|
||||
final List<WarehouseLocation> pagedLocations = totalCount > 0 && startIndex < totalCount
|
||||
? controller.warehouseLocations.sublist(startIndex, endIndex)
|
||||
: [];
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(ShadcnTheme.spacing6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -106,7 +147,17 @@ class _WarehouseLocationListRedesignState
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('총 ${totalCount}개 입고지', style: ShadcnTheme.bodyMuted),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('총 ${totalCount}개 입고지', style: ShadcnTheme.bodyMuted),
|
||||
if (controller.searchQuery.isNotEmpty)
|
||||
Text(
|
||||
'"${controller.searchQuery}" 검색 결과',
|
||||
style: ShadcnTheme.bodyMuted.copyWith(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
ShadcnButton(
|
||||
text: '입고지 추가',
|
||||
onPressed: _navigateToAdd,
|
||||
@@ -168,12 +219,27 @@ class _WarehouseLocationListRedesignState
|
||||
),
|
||||
|
||||
// 테이블 데이터
|
||||
if (pagedLocations.isEmpty)
|
||||
if (controller.isLoading)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(ShadcnTheme.spacing8),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
SizedBox(height: 8),
|
||||
Text('데이터를 불러오는 중...', style: ShadcnTheme.bodyMuted),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (pagedLocations.isEmpty)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(ShadcnTheme.spacing8),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'등록된 입고지가 없습니다.',
|
||||
controller.searchQuery.isNotEmpty
|
||||
? '검색 결과가 없습니다.'
|
||||
: '등록된 입고지가 없습니다.',
|
||||
style: ShadcnTheme.bodyMuted,
|
||||
),
|
||||
),
|
||||
@@ -306,7 +372,10 @@ class _WarehouseLocationListRedesignState
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user