feat: 장비 관리 API 연동 구현
- Equipment 관련 DTO 모델 생성 (Request/Response/List/History/In/Out/IO) - EquipmentRemoteDataSource 구현 (10개 API 엔드포인트) - EquipmentService 비즈니스 로직 구현 - Controller를 ChangeNotifier 패턴으로 개선 - 장비 목록 화면에 Provider 패턴 및 무한 스크롤 적용 - 장비 입고 화면 API 연동 및 비동기 처리 - DI 컨테이너에 Equipment 관련 의존성 등록 - API/Mock 데이터 소스 전환 가능 (Feature Flag) - API 통합 진행 상황 문서 업데이트
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:superport/screens/common/theme_shadcn.dart';
|
||||
import 'package:superport/screens/common/components/shadcn_components.dart';
|
||||
import 'package:superport/screens/equipment/controllers/equipment_list_controller.dart';
|
||||
@@ -20,7 +21,6 @@ class EquipmentListRedesign extends StatefulWidget {
|
||||
|
||||
class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
|
||||
late final EquipmentListController _controller;
|
||||
bool _isLoading = false;
|
||||
bool _showDetailedColumns = true;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
final ScrollController _horizontalScrollController = ScrollController();
|
||||
@@ -36,8 +36,14 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
|
||||
super.initState();
|
||||
_controller = EquipmentListController(dataService: MockDataService());
|
||||
_setInitialFilter();
|
||||
_controller.loadData(); // 원본과 동일하게 직접 호출
|
||||
print('DEBUG: Equipment count after loadData: ${_controller.equipments.length}'); // 디버그 정보
|
||||
|
||||
// 무한 스크롤 리스너 추가
|
||||
_verticalScrollController.addListener(_onScroll);
|
||||
|
||||
// API 호출을 위해 Future로 변경
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_controller.loadData(); // 비동기 호출
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -45,6 +51,7 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
|
||||
_searchController.dispose();
|
||||
_horizontalScrollController.dispose();
|
||||
_verticalScrollController.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -85,12 +92,12 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
|
||||
}
|
||||
|
||||
/// 데이터 로드
|
||||
void _loadData() {
|
||||
_controller.loadData();
|
||||
Future<void> _loadData({bool isRefresh = false}) async {
|
||||
await _controller.loadData(isRefresh: isRefresh);
|
||||
}
|
||||
|
||||
/// 상태 필터 변경
|
||||
void _onStatusFilterChanged(String status) {
|
||||
Future<void> _onStatusFilterChanged(String status) async {
|
||||
setState(() {
|
||||
_selectedStatus = status;
|
||||
// 상태 필터를 EquipmentStatus 상수로 변환
|
||||
@@ -103,9 +110,9 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
|
||||
} else if (status == 'rent') {
|
||||
_controller.selectedStatusFilter = EquipmentStatus.rent;
|
||||
}
|
||||
_controller.loadData();
|
||||
_currentPage = 1;
|
||||
});
|
||||
await _controller.changeStatusFilter(_controller.selectedStatusFilter);
|
||||
}
|
||||
|
||||
/// 검색 실행
|
||||
@@ -141,6 +148,17 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
|
||||
_controller.selectedEquipmentIds.contains('${e.id}:${e.status}'));
|
||||
}
|
||||
|
||||
/// 스크롤 이벤트 처리 (무한 스크롤)
|
||||
void _onScroll() {
|
||||
if (_verticalScrollController.position.pixels >=
|
||||
_verticalScrollController.position.maxScrollExtent * 0.8) {
|
||||
// 스크롤이 80% 이상 내려갔을 때 다음 페이지 로드
|
||||
if (!_controller.isLoading && _controller.hasMore) {
|
||||
_controller.loadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 필터링된 장비 목록 반환
|
||||
List<UnifiedEquipment> _getFilteredEquipments() {
|
||||
var equipments = _controller.equipments;
|
||||
@@ -325,24 +343,35 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 선택된 장비 개수
|
||||
final int selectedCount = _controller.getSelectedEquipmentCount();
|
||||
final int selectedInCount = _controller.getSelectedInStockCount();
|
||||
final int selectedOutCount = _controller.getSelectedEquipmentCountByStatus(EquipmentStatus.out);
|
||||
final int selectedRentCount = _controller.getSelectedEquipmentCountByStatus(EquipmentStatus.rent);
|
||||
return ChangeNotifierProvider<EquipmentListController>.value(
|
||||
value: _controller,
|
||||
child: Consumer<EquipmentListController>(
|
||||
builder: (context, controller, child) {
|
||||
// 선택된 장비 개수
|
||||
final int selectedCount = controller.getSelectedEquipmentCount();
|
||||
final int selectedInCount = controller.getSelectedInStockCount();
|
||||
final int selectedOutCount = controller.getSelectedEquipmentCountByStatus(EquipmentStatus.out);
|
||||
final int selectedRentCount = controller.getSelectedEquipmentCountByStatus(EquipmentStatus.rent);
|
||||
|
||||
return Container(
|
||||
color: ShadcnTheme.background,
|
||||
child: Column(
|
||||
children: [
|
||||
// 필터 및 액션 바
|
||||
_buildFilterBar(selectedCount, selectedInCount, selectedOutCount, selectedRentCount),
|
||||
return Container(
|
||||
color: ShadcnTheme.background,
|
||||
child: Column(
|
||||
children: [
|
||||
// 필터 및 액션 바
|
||||
_buildFilterBar(selectedCount, selectedInCount, selectedOutCount, selectedRentCount),
|
||||
|
||||
// 장비 테이블
|
||||
Expanded(
|
||||
child: _isLoading ? _buildLoadingState() : _buildEquipmentTable(),
|
||||
),
|
||||
],
|
||||
// 장비 테이블
|
||||
Expanded(
|
||||
child: controller.isLoading
|
||||
? _buildLoadingState()
|
||||
: controller.error != null
|
||||
? _buildErrorState()
|
||||
: _buildEquipmentTable(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -606,6 +635,30 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 에러 상태 위젯
|
||||
Widget _buildErrorState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.error_outline, size: 48, color: ShadcnTheme.destructive),
|
||||
const SizedBox(height: 16),
|
||||
Text('데이터를 불러오는 중 오류가 발생했습니다.', style: ShadcnTheme.bodyMuted),
|
||||
const SizedBox(height: 8),
|
||||
Text(_controller.error ?? '', style: ShadcnTheme.bodySmall),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () => _controller.loadData(isRefresh: true),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ShadcnTheme.primary,
|
||||
),
|
||||
child: const Text('다시 시도'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 테이블 너비 계산
|
||||
double _calculateTableWidth(List<UnifiedEquipment> pagedEquipments) {
|
||||
double totalWidth = 0;
|
||||
|
||||
Reference in New Issue
Block a user