refactor: 코드베이스 정리 및 에러 처리 개선
- API 클라이언트 및 인증 인터셉터 에러 처리 강화 - 의존성 주입 실패 시에도 앱 실행 가능하도록 개선 - 사용하지 않는 레거시 UI 컴포넌트 및 화면 제거 - pubspec.yaml 의존성 업데이트 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,696 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/screens/equipment/controllers/equipment_list_controller.dart';
|
||||
import 'package:superport/screens/equipment/widgets/equipment_table.dart';
|
||||
import 'package:superport/utils/equipment_display_helper.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
import 'package:superport/screens/common/main_layout.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart';
|
||||
import 'package:superport/screens/common/widgets/pagination.dart';
|
||||
|
||||
// 장비 목록 화면 (UI만 담당, 상태/로직/헬퍼/위젯 분리)
|
||||
class EquipmentListScreen extends StatefulWidget {
|
||||
final String currentRoute;
|
||||
const EquipmentListScreen({super.key, this.currentRoute = Routes.equipment});
|
||||
|
||||
@override
|
||||
State<EquipmentListScreen> createState() => _EquipmentListScreenState();
|
||||
}
|
||||
|
||||
class _EquipmentListScreenState extends State<EquipmentListScreen> {
|
||||
late final EquipmentListController _controller;
|
||||
bool _showDetailedColumns = true;
|
||||
final ScrollController _horizontalScrollController = ScrollController();
|
||||
final ScrollController _verticalScrollController = ScrollController();
|
||||
int _currentPage = 1;
|
||||
final int _pageSize = 10;
|
||||
String _searchKeyword = '';
|
||||
String _appliedSearchKeyword = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = EquipmentListController(dataService: MockDataService());
|
||||
_controller.loadData();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_adjustColumnsForScreenSize();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_setDefaultFilterByRoute();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(EquipmentListScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.currentRoute != widget.currentRoute) {
|
||||
_setDefaultFilterByRoute();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_horizontalScrollController.dispose();
|
||||
_verticalScrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 라우트에 따라 기본 필터 설정
|
||||
void _setDefaultFilterByRoute() {
|
||||
String? newFilter;
|
||||
if (widget.currentRoute == Routes.equipmentInList) {
|
||||
newFilter = EquipmentStatus.in_;
|
||||
} else if (widget.currentRoute == Routes.equipmentOutList) {
|
||||
newFilter = EquipmentStatus.out;
|
||||
} else if (widget.currentRoute == Routes.equipmentRentList) {
|
||||
newFilter = EquipmentStatus.rent;
|
||||
} else if (widget.currentRoute == Routes.equipment) {
|
||||
newFilter = null;
|
||||
}
|
||||
if ((newFilter != _controller.selectedStatusFilter) ||
|
||||
widget.currentRoute != Routes.equipment) {
|
||||
setState(() {
|
||||
_controller.selectedStatusFilter = newFilter;
|
||||
_controller.loadData();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 화면 크기에 따라 컬럼 표시 조정
|
||||
void _adjustColumnsForScreenSize() {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
setState(() {
|
||||
_showDetailedColumns = width > 900;
|
||||
});
|
||||
}
|
||||
|
||||
// 상태 필터 변경
|
||||
void _onStatusFilterChanged(String? status) {
|
||||
setState(() {
|
||||
_controller.changeStatusFilter(status);
|
||||
});
|
||||
}
|
||||
|
||||
// 장비 선택/해제
|
||||
void _onEquipmentSelected(int? id, String status, bool? isSelected) {
|
||||
setState(() {
|
||||
_controller.selectEquipment(id, status, isSelected);
|
||||
});
|
||||
}
|
||||
|
||||
// 출고 처리 버튼 핸들러
|
||||
void _handleOutEquipment() async {
|
||||
if (_controller.getSelectedInStockCount() == 0) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('출고할 장비를 선택해주세요.')));
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택된 장비들의 요약 정보를 가져와서 출고 폼으로 전달
|
||||
final selectedEquipmentsSummary =
|
||||
_controller.getSelectedEquipmentsSummary();
|
||||
|
||||
final result = await Navigator.pushNamed(
|
||||
context,
|
||||
Routes.equipmentOutAdd,
|
||||
arguments: {'selectedEquipments': selectedEquipmentsSummary},
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
setState(() {
|
||||
_controller.loadData();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 대여 처리 버튼 핸들러
|
||||
void _handleRentEquipment() async {
|
||||
if (_controller.getSelectedInStockCount() == 0) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('대여할 장비를 선택해주세요.')));
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택된 장비들의 요약 정보를 가져와서 대여 폼으로 전달
|
||||
final selectedEquipmentsSummary =
|
||||
_controller.getSelectedEquipmentsSummary();
|
||||
|
||||
// 현재는 대여 기능이 준비되지 않았으므로 간단히 스낵바 표시
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'${selectedEquipmentsSummary.length}개 장비 대여 기능은 준비 중입니다.',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 폐기 처리 버튼 핸들러
|
||||
void _handleDisposeEquipment() {
|
||||
if (_controller.getSelectedInStockCount() == 0) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('폐기할 장비를 선택해주세요.')));
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택된 장비들의 요약 정보를 가져옴
|
||||
final selectedEquipmentsSummary =
|
||||
_controller.getSelectedEquipmentsSummary();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
title: const Text('폐기 확인'),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'선택한 ${selectedEquipmentsSummary.length}개 장비를 폐기하시겠습니까?',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'폐기할 장비 목록:',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...selectedEquipmentsSummary.map((equipmentData) {
|
||||
final equipment = equipmentData['equipment'] as Equipment;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
'${equipment.manufacturer} ${equipment.name} (${equipment.quantity}개)',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// 여기에 폐기 로직 추가 예정
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('폐기 기능은 준비 중입니다.')),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('폐기'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 카테고리 축약 표기 함수 (예: 컴... > 태... > 안드로...)
|
||||
String _shortenCategory(String category) {
|
||||
if (category.length <= 2) return category;
|
||||
return category.substring(0, 2) + '...';
|
||||
}
|
||||
|
||||
// 카테고리 툴팁 위젯 (UI만 담당, 축약 표기 적용)
|
||||
Widget _buildCategoryWithTooltip(UnifiedEquipment equipment) {
|
||||
final fullCategory = EquipmentDisplayHelper.formatCategory(
|
||||
equipment.equipment.category,
|
||||
equipment.equipment.subCategory,
|
||||
equipment.equipment.subSubCategory,
|
||||
);
|
||||
// 축약 표기 적용
|
||||
final shortCategory = [
|
||||
_shortenCategory(equipment.equipment.category),
|
||||
_shortenCategory(equipment.equipment.subCategory),
|
||||
_shortenCategory(equipment.equipment.subSubCategory),
|
||||
].join(' > ');
|
||||
return Tooltip(message: fullCategory, child: Text(shortCategory));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final maxContentWidth = screenWidth > 1200 ? 1200.0 : screenWidth - 32;
|
||||
String screenTitle = '장비 목록';
|
||||
if (widget.currentRoute == Routes.equipmentInList) {
|
||||
screenTitle = '입고된 장비';
|
||||
} else if (widget.currentRoute == Routes.equipmentOutList) {
|
||||
screenTitle = '출고된 장비';
|
||||
} else if (widget.currentRoute == Routes.equipmentRentList) {
|
||||
screenTitle = '대여된 장비';
|
||||
}
|
||||
final int totalCount = _controller.equipments.length;
|
||||
final List<UnifiedEquipment> filteredEquipments =
|
||||
_appliedSearchKeyword.isEmpty
|
||||
? _controller.equipments
|
||||
: _controller.equipments.where((e) {
|
||||
final keyword = _appliedSearchKeyword.toLowerCase();
|
||||
// 모든 주요 필드에서 검색
|
||||
return [
|
||||
e.equipment.manufacturer,
|
||||
e.equipment.name,
|
||||
e.equipment.category,
|
||||
e.equipment.subCategory,
|
||||
e.equipment.subSubCategory,
|
||||
e.equipment.serialNumber ?? '',
|
||||
e.equipment.barcode ?? '',
|
||||
e.equipment.remark ?? '',
|
||||
e.equipment.warrantyLicense ?? '',
|
||||
e.notes ?? '',
|
||||
].any((field) => field.toLowerCase().contains(keyword));
|
||||
}).toList();
|
||||
final int filteredCount = filteredEquipments.length;
|
||||
final int startIndex = (_currentPage - 1) * _pageSize;
|
||||
final int endIndex =
|
||||
(startIndex + _pageSize) > filteredCount
|
||||
? filteredCount
|
||||
: (startIndex + _pageSize);
|
||||
final pagedEquipments = filteredEquipments.sublist(startIndex, endIndex);
|
||||
|
||||
// 선택된 장비 개수
|
||||
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 MainLayout(
|
||||
title: screenTitle,
|
||||
currentRoute: widget.currentRoute,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_showDetailedColumns ? Icons.view_column : Icons.view_compact,
|
||||
color: Colors.grey,
|
||||
),
|
||||
tooltip: _showDetailedColumns ? '간소화된 보기' : '상세 보기',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_showDetailedColumns = !_showDetailedColumns;
|
||||
});
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_controller.loadData();
|
||||
_currentPage = 1;
|
||||
});
|
||||
},
|
||||
color: Colors.grey,
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
width: maxContentWidth,
|
||||
padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Text(
|
||||
screenTitle,
|
||||
style: AppThemeTailwind.headingStyle,
|
||||
),
|
||||
),
|
||||
if (selectedCount > 0)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 16,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
'$selectedCount개 선택됨',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
if (widget.currentRoute == Routes.equipmentInList)
|
||||
Row(
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed:
|
||||
selectedInCount > 0 ? _handleOutEquipment : null,
|
||||
icon: const Icon(
|
||||
Icons.exit_to_app,
|
||||
color: Colors.white,
|
||||
),
|
||||
label: const Text(
|
||||
'출고',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
disabledBackgroundColor: Colors.blue.withOpacity(0.5),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 10,
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () async {
|
||||
final result = await Navigator.pushNamed(
|
||||
context,
|
||||
Routes.equipmentInAdd,
|
||||
);
|
||||
if (result == true) {
|
||||
setState(() {
|
||||
_controller.loadData();
|
||||
_currentPage = 1;
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.add, color: Colors.white),
|
||||
label: const Text(
|
||||
'입고',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 10,
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
SizedBox(
|
||||
width: 220,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration: const InputDecoration(
|
||||
hintText: '장비 검색',
|
||||
prefixIcon: Icon(Icons.search),
|
||||
border: OutlineInputBorder(),
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 12,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchKeyword = value;
|
||||
});
|
||||
},
|
||||
onSubmitted: (value) {
|
||||
setState(() {
|
||||
_appliedSearchKeyword = value;
|
||||
_currentPage = 1;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
tooltip: '검색',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_appliedSearchKeyword = _searchKeyword;
|
||||
_currentPage = 1;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 출고 목록 화면일 때 버튼들
|
||||
if (widget.currentRoute == Routes.equipmentOutList)
|
||||
Row(
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed:
|
||||
selectedOutCount > 0
|
||||
? () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('재입고 기능은 준비 중입니다.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(
|
||||
Icons.assignment_return,
|
||||
color: Colors.white,
|
||||
),
|
||||
label: const Text(
|
||||
'재입고',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
disabledBackgroundColor: Colors.green.withOpacity(
|
||||
0.5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 10,
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed:
|
||||
selectedOutCount > 0
|
||||
? () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('수리 요청 기능은 준비 중입니다.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.build, color: Colors.white),
|
||||
label: const Text(
|
||||
'수리 요청',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
disabledBackgroundColor: Colors.orange.withOpacity(
|
||||
0.5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 10,
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 대여 목록 화면일 때 버튼들
|
||||
if (widget.currentRoute == Routes.equipmentRentList)
|
||||
Row(
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed:
|
||||
selectedRentCount > 0
|
||||
? () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('대여 반납 기능은 준비 중입니다.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(
|
||||
Icons.keyboard_return,
|
||||
color: Colors.white,
|
||||
),
|
||||
label: const Text(
|
||||
'반납',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
disabledBackgroundColor: Colors.green.withOpacity(
|
||||
0.5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 10,
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed:
|
||||
selectedRentCount > 0
|
||||
? () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('대여 연장 기능은 준비 중입니다.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.date_range, color: Colors.white),
|
||||
label: const Text(
|
||||
'연장',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
disabledBackgroundColor: Colors.blue.withOpacity(0.5),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 10,
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child:
|
||||
pagedEquipments.isEmpty
|
||||
? const Center(child: Text('장비 정보가 없습니다.'))
|
||||
: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: maxContentWidth,
|
||||
),
|
||||
child: EquipmentTable(
|
||||
equipments: pagedEquipments,
|
||||
selectedEquipmentIds:
|
||||
_controller.selectedEquipmentIds,
|
||||
showDetailedColumns: _showDetailedColumns,
|
||||
onEquipmentSelected: _onEquipmentSelected,
|
||||
getOutEquipmentInfo:
|
||||
_controller.getOutEquipmentInfo,
|
||||
buildCategoryWithTooltip: _buildCategoryWithTooltip,
|
||||
// 수정 버튼 동작: 입고 폼(수정 모드)로 이동
|
||||
onEdit: (id, status) async {
|
||||
if (status == EquipmentStatus.in_) {
|
||||
final result = await Navigator.pushNamed(
|
||||
context,
|
||||
Routes.equipmentInEdit,
|
||||
arguments: id,
|
||||
);
|
||||
if (result == true) {
|
||||
setState(() {
|
||||
_controller.loadData();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 출고/대여 등은 별도 폼으로 이동 필요시 구현
|
||||
}
|
||||
},
|
||||
// 삭제 버튼 동작: 삭제 다이얼로그 및 삭제 처리
|
||||
onDelete: (id, status) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
title: const Text('삭제 확인'),
|
||||
content: const Text('이 장비 정보를 삭제하시겠습니까?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
() => Navigator.pop(context),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
// 입고/출고 상태에 따라 삭제 처리
|
||||
if (status ==
|
||||
EquipmentStatus.in_) {
|
||||
MockDataService()
|
||||
.deleteEquipmentIn(id);
|
||||
} else if (status ==
|
||||
EquipmentStatus.out) {
|
||||
MockDataService()
|
||||
.deleteEquipmentOut(id);
|
||||
}
|
||||
_controller.loadData();
|
||||
});
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('삭제'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
getSelectedInStockCount:
|
||||
_controller.getSelectedInStockCount,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (totalCount > _pageSize)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Pagination(
|
||||
totalCount: filteredCount,
|
||||
currentPage: _currentPage,
|
||||
pageSize: _pageSize,
|
||||
onPageChanged: (page) {
|
||||
setState(() {
|
||||
_currentPage = page;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart';
|
||||
import 'package:superport/screens/equipment/widgets/equipment_status_chip.dart';
|
||||
import 'package:superport/screens/equipment/widgets/equipment_out_info.dart';
|
||||
import 'package:superport/utils/equipment_display_helper.dart';
|
||||
|
||||
// 장비 목록 테이블 위젯 (SRP, 재사용성 강화)
|
||||
class EquipmentTable extends StatelessWidget {
|
||||
final List<UnifiedEquipment> equipments;
|
||||
final Set<String> selectedEquipmentIds;
|
||||
final bool showDetailedColumns;
|
||||
final void Function(int? id, String status, bool? isSelected)
|
||||
onEquipmentSelected;
|
||||
final String Function(int equipmentId, String infoType) getOutEquipmentInfo;
|
||||
final Widget Function(UnifiedEquipment equipment) buildCategoryWithTooltip;
|
||||
final void Function(int id, String status) onEdit;
|
||||
final void Function(int id, String status) onDelete;
|
||||
final int Function() getSelectedInStockCount;
|
||||
|
||||
const EquipmentTable({
|
||||
super.key,
|
||||
required this.equipments,
|
||||
required this.selectedEquipmentIds,
|
||||
required this.showDetailedColumns,
|
||||
required this.onEquipmentSelected,
|
||||
required this.getOutEquipmentInfo,
|
||||
required this.buildCategoryWithTooltip,
|
||||
required this.onEdit,
|
||||
required this.onDelete,
|
||||
required this.getSelectedInStockCount,
|
||||
});
|
||||
|
||||
// 출고 정보(간소화 모드) 위젯
|
||||
Widget _buildCompactOutInfo(int equipmentId) {
|
||||
final company = getOutEquipmentInfo(equipmentId, 'company');
|
||||
final manager = getOutEquipmentInfo(equipmentId, 'manager');
|
||||
final license = getOutEquipmentInfo(equipmentId, 'license');
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
EquipmentOutInfoIcon(infoType: 'company', text: company),
|
||||
const SizedBox(height: 2),
|
||||
EquipmentOutInfoIcon(infoType: 'manager', text: manager),
|
||||
const SizedBox(height: 2),
|
||||
EquipmentOutInfoIcon(infoType: 'license', text: license),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 카테고리 툴팁 위젯 (UI만 담당, 축약 표기 적용)
|
||||
Widget _buildCategoryWithTooltip(UnifiedEquipment equipment) {
|
||||
// 한글 라벨로 표기
|
||||
final fullCategory =
|
||||
'대분류: ${equipment.equipment.category} / 중분류: ${equipment.equipment.subCategory} / 소분류: ${equipment.equipment.subSubCategory}';
|
||||
final shortCategory = [
|
||||
_shortenCategory(equipment.equipment.category),
|
||||
_shortenCategory(equipment.equipment.subCategory),
|
||||
_shortenCategory(equipment.equipment.subSubCategory),
|
||||
].join(' > ');
|
||||
return Tooltip(message: fullCategory, child: Text(shortCategory));
|
||||
}
|
||||
|
||||
// 카테고리 축약 표기 함수 (예: 컴...)
|
||||
String _shortenCategory(String category) {
|
||||
if (category.length <= 2) return category;
|
||||
return category.substring(0, 2) + '...';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DataTable(
|
||||
headingRowHeight: 48,
|
||||
dataRowMinHeight: 48,
|
||||
dataRowMaxHeight: 60,
|
||||
columnSpacing: 10,
|
||||
horizontalMargin: 16,
|
||||
columns: [
|
||||
const DataColumn(label: SizedBox(width: 32, child: Text('선택'))),
|
||||
const DataColumn(label: SizedBox(width: 32, child: Text('번호'))),
|
||||
if (showDetailedColumns)
|
||||
const DataColumn(label: SizedBox(width: 60, child: Text('제조사'))),
|
||||
const DataColumn(label: SizedBox(width: 90, child: Text('장비명'))),
|
||||
if (showDetailedColumns)
|
||||
const DataColumn(label: SizedBox(width: 110, child: Text('분류'))),
|
||||
if (showDetailedColumns)
|
||||
const DataColumn(label: SizedBox(width: 60, child: Text('장비 유형'))),
|
||||
if (showDetailedColumns)
|
||||
const DataColumn(label: SizedBox(width: 70, child: Text('시리얼번호'))),
|
||||
const DataColumn(label: SizedBox(width: 38, child: Text('수량'))),
|
||||
const DataColumn(label: SizedBox(width: 80, child: Text('변경 일자'))),
|
||||
const DataColumn(label: SizedBox(width: 44, child: Text('상태'))),
|
||||
if (showDetailedColumns) ...[
|
||||
const DataColumn(label: SizedBox(width: 90, child: Text('출고 회사'))),
|
||||
const DataColumn(label: SizedBox(width: 60, child: Text('담당자'))),
|
||||
const DataColumn(label: SizedBox(width: 60, child: Text('라이센스'))),
|
||||
] else
|
||||
const DataColumn(label: SizedBox(width: 110, child: Text('출고 정보'))),
|
||||
const DataColumn(label: SizedBox(width: 60, child: Text('관리'))),
|
||||
],
|
||||
rows:
|
||||
equipments.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final equipment = entry.value;
|
||||
final bool isInStock = equipment.status == 'I';
|
||||
final bool isOutStock = equipment.status == 'O';
|
||||
return DataRow(
|
||||
color: MaterialStateProperty.resolveWith<Color?>(
|
||||
(Set<MaterialState> states) =>
|
||||
index % 2 == 0 ? Colors.grey[50] : null,
|
||||
),
|
||||
cells: [
|
||||
DataCell(
|
||||
Checkbox(
|
||||
value: selectedEquipmentIds.contains(
|
||||
'${equipment.id}:${equipment.status}',
|
||||
),
|
||||
onChanged:
|
||||
(isSelected) => onEquipmentSelected(
|
||||
equipment.id,
|
||||
equipment.status,
|
||||
isSelected,
|
||||
),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
DataCell(Text('${index + 1}')),
|
||||
if (showDetailedColumns)
|
||||
DataCell(
|
||||
Text(
|
||||
EquipmentDisplayHelper.formatManufacturer(
|
||||
equipment.equipment.manufacturer,
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Text(
|
||||
EquipmentDisplayHelper.formatEquipmentName(
|
||||
equipment.equipment.name,
|
||||
),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
if (showDetailedColumns)
|
||||
DataCell(buildCategoryWithTooltip(equipment)),
|
||||
if (showDetailedColumns)
|
||||
DataCell(
|
||||
Text(
|
||||
equipment.status == 'I' &&
|
||||
equipment is UnifiedEquipment &&
|
||||
equipment.type != null
|
||||
? equipment.type!
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
if (showDetailedColumns)
|
||||
DataCell(
|
||||
Text(
|
||||
EquipmentDisplayHelper.formatSerialNumber(
|
||||
equipment.equipment.serialNumber,
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Text(
|
||||
'${equipment.equipment.quantity}',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Text(EquipmentDisplayHelper.formatDate(equipment.date)),
|
||||
),
|
||||
DataCell(EquipmentStatusChip(status: equipment.status)),
|
||||
if (showDetailedColumns) ...[
|
||||
DataCell(
|
||||
Text(
|
||||
isOutStock
|
||||
? getOutEquipmentInfo(equipment.id!, 'company')
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Text(
|
||||
isOutStock
|
||||
? getOutEquipmentInfo(equipment.id!, 'manager')
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Text(
|
||||
isOutStock
|
||||
? getOutEquipmentInfo(equipment.id!, 'license')
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
] else
|
||||
DataCell(
|
||||
isOutStock
|
||||
? _buildCompactOutInfo(equipment.id!)
|
||||
: const Text('-'),
|
||||
),
|
||||
DataCell(
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.edit,
|
||||
color: Colors.blue,
|
||||
size: 20,
|
||||
),
|
||||
constraints: const BoxConstraints(),
|
||||
padding: const EdgeInsets.all(5),
|
||||
onPressed:
|
||||
() => onEdit(equipment.id!, equipment.status),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
color: Colors.red,
|
||||
size: 20,
|
||||
),
|
||||
constraints: const BoxConstraints(),
|
||||
padding: const EdgeInsets.all(5),
|
||||
onPressed:
|
||||
() => onDelete(equipment.id!, equipment.status),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user