사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:superport/screens/common/theme_shadcn.dart';
|
||||
import 'package:superport/screens/common/components/shadcn_components.dart';
|
||||
import 'package:superport/screens/common/widgets/pagination.dart';
|
||||
@@ -9,15 +10,13 @@ import 'package:superport/screens/common/layouts/base_list_screen.dart';
|
||||
import 'package:superport/screens/equipment/controllers/equipment_list_controller.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:superport/utils/equipment_display_helper.dart';
|
||||
import 'package:superport/screens/equipment/widgets/equipment_history_dialog.dart';
|
||||
|
||||
/// shadcn/ui 스타일로 재설계된 장비 관리 화면
|
||||
class EquipmentList extends StatefulWidget {
|
||||
final String currentRoute;
|
||||
|
||||
const EquipmentList({Key? key, this.currentRoute = Routes.equipment})
|
||||
: super(key: key);
|
||||
const EquipmentList({super.key, this.currentRoute = Routes.equipment});
|
||||
|
||||
@override
|
||||
State<EquipmentList> createState() => _EquipmentListState();
|
||||
@@ -28,7 +27,6 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
bool _showDetailedColumns = true;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
final ScrollController _horizontalScrollController = ScrollController();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
String _selectedStatus = 'all';
|
||||
// String _searchKeyword = ''; // Removed - unused field
|
||||
String _appliedSearchKeyword = '';
|
||||
@@ -92,10 +90,6 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
print('DEBUG: Initial filter set - route: ${widget.currentRoute}, status: $_selectedStatus, filter: ${_controller.selectedStatusFilter}'); // 디버그 정보
|
||||
}
|
||||
|
||||
/// 데이터 로드
|
||||
Future<void> _loadData({bool isRefresh = false}) async {
|
||||
await _controller.loadData(isRefresh: isRefresh);
|
||||
}
|
||||
|
||||
/// 상태 필터 변경
|
||||
Future<void> _onStatusFilterChanged(String status) async {
|
||||
@@ -144,25 +138,6 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
_controller.updateSearchKeyword(_searchController.text);
|
||||
}
|
||||
|
||||
/// 장비 선택/해제
|
||||
void _onEquipmentSelected(int? id, String status, bool? isSelected) {
|
||||
if (id == null) return;
|
||||
|
||||
// UnifiedEquipment를 찾아서 선택/해제
|
||||
UnifiedEquipment? equipment;
|
||||
try {
|
||||
equipment = _controller.items.firstWhere(
|
||||
(e) => e.equipment.id == id && e.status == status,
|
||||
);
|
||||
} catch (e) {
|
||||
// 해당하는 장비를 찾지 못함
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_controller.selectEquipment(equipment!);
|
||||
});
|
||||
}
|
||||
|
||||
/// 전체 선택/해제
|
||||
void _onSelectAll(bool? value) {
|
||||
@@ -194,18 +169,13 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
equipments = equipments.where((e) {
|
||||
final keyword = _appliedSearchKeyword.toLowerCase();
|
||||
return [
|
||||
e.equipment.manufacturer,
|
||||
e.equipment.equipmentNumber, // name → equipmentNumber (메인 필드)
|
||||
e.equipment.modelName ?? '', // 모델명 추가
|
||||
e.equipment.category1, // category → category1 (메인 필드)
|
||||
e.equipment.category2, // subCategory → category2 (메인 필드)
|
||||
e.equipment.category3, // subSubCategory → category3 (메인 필드)
|
||||
e.equipment.serialNumber ?? '',
|
||||
e.equipment.barcode ?? '',
|
||||
e.equipment.remark ?? '',
|
||||
e.equipment.warrantyLicense ?? '',
|
||||
e.notes ?? '',
|
||||
].any((field) => field.toLowerCase().contains(keyword));
|
||||
e.equipment.model?.vendor?.name ?? '', // Vendor 이름
|
||||
e.equipment.serialNumber ?? '', // 시리얼 번호 (메인 필드)
|
||||
e.equipment.model?.name ?? '', // Model 이름
|
||||
e.equipment.serialNumber ?? '', // 시리얼 번호 (중복 제거)
|
||||
e.equipment.barcode ?? '', // 바코드
|
||||
e.equipment.remark ?? '', // 비고
|
||||
].any((field) => field.toLowerCase().contains(keyword.toLowerCase()));
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@@ -215,8 +185,11 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
/// 출고 처리 버튼 핸들러
|
||||
void _handleOutEquipment() async {
|
||||
if (_controller.getSelectedInStockCount() == 0) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('출고할 장비를 선택해주세요.')),
|
||||
ShadToaster.of(context).show(
|
||||
const ShadToast(
|
||||
title: Text('알림'),
|
||||
description: Text('출고할 장비를 선택해주세요.'),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -241,16 +214,20 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
/// 대여 처리 버튼 핸들러
|
||||
void _handleRentEquipment() async {
|
||||
if (_controller.getSelectedInStockCount() == 0) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('대여할 장비를 선택해주세요.')),
|
||||
ShadToaster.of(context).show(
|
||||
const ShadToast(
|
||||
title: Text('알림'),
|
||||
description: Text('대여할 장비를 선택해주세요.'),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final selectedEquipmentsSummary = _controller.getSelectedEquipmentsSummary();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${selectedEquipmentsSummary.length}개 장비 대여 기능은 준비 중입니다.'),
|
||||
ShadToaster.of(context).show(
|
||||
ShadToast(
|
||||
title: const Text('알림'),
|
||||
description: Text('${selectedEquipmentsSummary.length}개 장비 대여 기능은 준비 중입니다.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -262,8 +239,11 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
.toList();
|
||||
|
||||
if (selectedEquipments.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('폐기할 장비를 선택해주세요. (이미 폐기된 장비는 제외)')),
|
||||
ShadToaster.of(context).show(
|
||||
const ShadToast(
|
||||
title: Text('알림'),
|
||||
description: Text('폐기할 장비를 선택해주세요. (이미 폐기된 장비는 제외)'),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -271,11 +251,11 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
// 폐기 사유 입력을 위한 컨트롤러
|
||||
final TextEditingController reasonController = TextEditingController();
|
||||
|
||||
final result = await showDialog<bool>(
|
||||
final result = await showShadDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
builder: (context) => ShadDialog(
|
||||
title: const Text('폐기 확인'),
|
||||
content: SingleChildScrollView(
|
||||
description: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -289,7 +269,7 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
'${equipment.manufacturer} ${equipment.equipmentNumber}', // name → equipmentNumber
|
||||
'${equipment.model?.vendor?.name ?? 'N/A'} ${equipment.serialNumber}', // Vendor + Equipment Number
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
);
|
||||
@@ -297,25 +277,22 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
const SizedBox(height: 16),
|
||||
const Text('폐기 사유:', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
ShadInputFormField(
|
||||
controller: reasonController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '폐기 사유를 입력해주세요',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
placeholder: const Text('폐기 사유를 입력해주세요'),
|
||||
maxLines: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
ShadButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
TextButton(
|
||||
ShadButton.destructive(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('폐기', style: TextStyle(color: Colors.red)),
|
||||
child: const Text('폐기'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -323,11 +300,13 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
|
||||
if (result == true) {
|
||||
// 로딩 다이얼로그 표시
|
||||
showDialog(
|
||||
showShadDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
builder: (context) => const ShadDialog(
|
||||
child: Center(
|
||||
child: ShadProgress(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -338,8 +317,11 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
|
||||
if (mounted) {
|
||||
Navigator.pop(context); // 로딩 다이얼로그 닫기
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('선택한 장비가 폐기 처리되었습니다.')),
|
||||
ShadToaster.of(context).show(
|
||||
const ShadToast(
|
||||
title: Text('폐기 완료'),
|
||||
description: Text('선택한 장비가 폐기 처리되었습니다.'),
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_controller.loadData(isRefresh: true);
|
||||
@@ -348,8 +330,11 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
Navigator.pop(context); // 로딩 다이얼로그 닫기
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('폐기 처리 실패: ${e.toString()}')),
|
||||
ShadToaster.of(context).show(
|
||||
ShadToast.destructive(
|
||||
title: const Text('폐기 실패'),
|
||||
description: Text(e.toString()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -382,17 +367,17 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
|
||||
/// 삭제 핸들러
|
||||
void _handleDelete(UnifiedEquipment equipment) {
|
||||
showDialog(
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
builder: (context) => ShadDialog(
|
||||
title: const Text('삭제 확인'),
|
||||
content: const Text('이 장비 정보를 삭제하시겠습니까?'),
|
||||
description: const Text('이 장비 정보를 삭제하시겠습니까?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
ShadButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
TextButton(
|
||||
ShadButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
try {
|
||||
@@ -400,48 +385,31 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
await _controller.deleteEquipment(equipment.equipment.id!, equipment.status);
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('장비가 삭제되었습니다.')),
|
||||
ShadToaster.of(context).show(
|
||||
ShadToast(
|
||||
title: const Text('장비 삭제'),
|
||||
description: const Text('장비가 삭제되었습니다.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('삭제 실패: ${e.toString()}'),
|
||||
backgroundColor: Colors.red,
|
||||
ShadToaster.of(context).show(
|
||||
ShadToast.destructive(
|
||||
title: const Text('삭제 실패'),
|
||||
description: Text(e.toString()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Text('삭제', style: TextStyle(color: Colors.red)),
|
||||
child: const Text('삭제'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 이력 보기 핸들러
|
||||
void _handleHistory(UnifiedEquipment equipment) async {
|
||||
if (equipment.equipment.id == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('장비 ID가 없습니다.')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 팝업 다이얼로그로 이력 표시
|
||||
final result = await EquipmentHistoryDialog.show(
|
||||
context: context,
|
||||
equipmentId: equipment.equipment.id!,
|
||||
equipmentName: '${equipment.equipment.manufacturer} ${equipment.equipment.equipmentNumber}', // name → equipmentNumber
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
_controller.loadData(isRefresh: true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -511,7 +479,7 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
controller: _searchController,
|
||||
onSubmitted: (_) => _onSearch(),
|
||||
decoration: InputDecoration(
|
||||
hintText: '장비명, 제조사, 카테고리, 시리얼번호 등...',
|
||||
hintText: '제조사, 모델명, 시리얼번호, 바코드 등...',
|
||||
hintStyle: TextStyle(color: ShadcnTheme.mutedForeground.withValues(alpha: 0.8), fontSize: 14),
|
||||
prefixIcon: Icon(Icons.search, color: ShadcnTheme.muted, size: 20),
|
||||
border: InputBorder.none,
|
||||
@@ -539,22 +507,21 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// 상태 필터 드롭다운 (캐시된 데이터 사용)
|
||||
Container(
|
||||
SizedBox(
|
||||
height: 40,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.card,
|
||||
border: Border.all(color: Colors.black),
|
||||
borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
value: _selectedStatus,
|
||||
onChanged: (value) => _onStatusFilterChanged(value!),
|
||||
style: TextStyle(fontSize: 14, color: ShadcnTheme.foreground),
|
||||
icon: const Icon(Icons.arrow_drop_down, size: 20),
|
||||
items: _buildStatusDropdownItems(),
|
||||
width: 150,
|
||||
child: ShadSelect<String>(
|
||||
selectedOptionBuilder: (context, value) => Text(
|
||||
_getStatusDisplayText(value),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
placeholder: const Text('상태 선택'),
|
||||
options: _buildStatusSelectOptions(),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
_onStatusFilterChanged(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -573,12 +540,13 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
// TODO: 실제 권한 체크 로직 추가 필요
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
ShadCheckbox(
|
||||
value: _controller.includeInactive,
|
||||
onChanged: (_) => setState(() {
|
||||
_controller.toggleIncludeInactive();
|
||||
}),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text('비활성 포함'),
|
||||
],
|
||||
),
|
||||
@@ -635,8 +603,11 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
ShadcnButton(
|
||||
text: '재입고',
|
||||
onPressed: selectedOutCount > 0
|
||||
? () => ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('재입고 기능은 준비 중입니다.')),
|
||||
? () => ShadToaster.of(context).show(
|
||||
const ShadToast(
|
||||
title: Text('알림'),
|
||||
description: Text('재입고 기능은 준비 중입니다.'),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
variant: selectedOutCount > 0 ? ShadcnButtonVariant.primary : ShadcnButtonVariant.secondary,
|
||||
@@ -646,8 +617,11 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
ShadcnButton(
|
||||
text: '수리 요청',
|
||||
onPressed: selectedOutCount > 0
|
||||
? () => ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('수리 요청 기능은 준비 중입니다.')),
|
||||
? () => ShadToaster.of(context).show(
|
||||
const ShadToast(
|
||||
title: Text('알림'),
|
||||
description: Text('수리 요청 기능은 준비 중입니다.'),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
variant: selectedOutCount > 0 ? ShadcnButtonVariant.destructive : ShadcnButtonVariant.secondary,
|
||||
@@ -661,8 +635,11 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
ShadcnButton(
|
||||
text: '반납',
|
||||
onPressed: selectedRentCount > 0
|
||||
? () => ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('대여 반납 기능은 준비 중입니다.')),
|
||||
? () => ShadToaster.of(context).show(
|
||||
const ShadToast(
|
||||
title: Text('알림'),
|
||||
description: Text('대여 반납 기능은 준비 중입니다.'),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
variant: selectedRentCount > 0 ? ShadcnButtonVariant.primary : ShadcnButtonVariant.secondary,
|
||||
@@ -672,8 +649,11 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
ShadcnButton(
|
||||
text: '연장',
|
||||
onPressed: selectedRentCount > 0
|
||||
? () => ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('대여 연장 기능은 준비 중입니다.')),
|
||||
? () => ShadToaster.of(context).show(
|
||||
const ShadToast(
|
||||
title: Text('알림'),
|
||||
description: Text('대여 연장 기능은 준비 중입니다.'),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
variant: selectedRentCount > 0 ? ShadcnButtonVariant.primary : ShadcnButtonVariant.secondary,
|
||||
@@ -795,180 +775,212 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 유연한 테이블 빌더
|
||||
/// 유연한 테이블 빌더 - Virtual Scrolling 적용
|
||||
Widget _buildFlexibleTable(List<UnifiedEquipment> pagedEquipments, {required bool useExpanded}) {
|
||||
final hasOutOrRent = pagedEquipments.any((e) =>
|
||||
e.status == EquipmentStatus.out || e.status == EquipmentStatus.rent
|
||||
);
|
||||
|
||||
// 헤더를 별도로 빌드
|
||||
Widget header = Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: ShadcnTheme.spacing4,
|
||||
vertical: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.muted.withValues(alpha: 0.3),
|
||||
border: Border(
|
||||
bottom: BorderSide(color: Colors.black),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 체크박스
|
||||
_buildDataCell(
|
||||
ShadCheckbox(
|
||||
value: _isAllSelected(),
|
||||
onChanged: (bool? value) => _onSelectAll(value),
|
||||
),
|
||||
flex: 1,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 40,
|
||||
),
|
||||
// 번호
|
||||
_buildHeaderCell('번호', flex: 1, useExpanded: useExpanded, minWidth: 50),
|
||||
// 제조사
|
||||
_buildHeaderCell('제조사', flex: 3, useExpanded: useExpanded, minWidth: 120),
|
||||
// 장비번호
|
||||
_buildHeaderCell('장비번호', flex: 3, useExpanded: useExpanded, minWidth: 120),
|
||||
// 모델명
|
||||
_buildHeaderCell('모델명', flex: 3, useExpanded: useExpanded, minWidth: 120),
|
||||
// 상세 정보 (조건부) - 바코드로 변경
|
||||
if (_showDetailedColumns) ...[
|
||||
_buildHeaderCell('바코드', flex: 3, useExpanded: useExpanded, minWidth: 120),
|
||||
],
|
||||
// 수량
|
||||
_buildHeaderCell('수량', flex: 1, useExpanded: useExpanded, minWidth: 50),
|
||||
// 재고 상태
|
||||
_buildHeaderCell('재고', flex: 2, useExpanded: useExpanded, minWidth: 80),
|
||||
// 상태
|
||||
_buildHeaderCell('상태', flex: 2, useExpanded: useExpanded, minWidth: 70),
|
||||
// 입출고일
|
||||
_buildHeaderCell('입출고일', flex: 2, useExpanded: useExpanded, minWidth: 80),
|
||||
// 관리
|
||||
_buildHeaderCell('관리', flex: 2, useExpanded: useExpanded, minWidth: 90),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// 빈 상태 처리
|
||||
if (pagedEquipments.isEmpty) {
|
||||
return Column(
|
||||
children: [
|
||||
header,
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'데이터가 없습니다',
|
||||
style: ShadcnTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Virtual Scrolling을 위한 CustomScrollView 사용
|
||||
return Column(
|
||||
children: [
|
||||
// 테이블 헤더
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: ShadcnTheme.spacing4,
|
||||
vertical: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.muted.withValues(alpha: 0.3),
|
||||
border: Border(
|
||||
bottom: BorderSide(color: Colors.black),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 체크박스
|
||||
_buildDataCell(
|
||||
Checkbox(
|
||||
value: _isAllSelected(),
|
||||
onChanged: _onSelectAll,
|
||||
header, // 헤더는 고정
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: ScrollController(),
|
||||
itemCount: pagedEquipments.length,
|
||||
itemBuilder: (context, index) {
|
||||
final UnifiedEquipment equipment = pagedEquipments[index];
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: ShadcnTheme.spacing4,
|
||||
vertical: 4,
|
||||
),
|
||||
flex: 1,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 40,
|
||||
),
|
||||
// 번호
|
||||
_buildHeaderCell('번호', flex: 1, useExpanded: useExpanded, minWidth: 50),
|
||||
// 제조사
|
||||
_buildHeaderCell('제조사', flex: 3, useExpanded: useExpanded, minWidth: 120),
|
||||
// 장비번호
|
||||
_buildHeaderCell('장비번호', flex: 3, useExpanded: useExpanded, minWidth: 120),
|
||||
// 모델명
|
||||
_buildHeaderCell('모델명', flex: 3, useExpanded: useExpanded, minWidth: 120),
|
||||
// 상세 정보 (조건부)
|
||||
if (_showDetailedColumns) ...[
|
||||
_buildHeaderCell('시리얼번호', flex: 3, useExpanded: useExpanded, minWidth: 120),
|
||||
],
|
||||
// 수량
|
||||
_buildHeaderCell('수량', flex: 1, useExpanded: useExpanded, minWidth: 50),
|
||||
// 상태
|
||||
_buildHeaderCell('상태', flex: 2, useExpanded: useExpanded, minWidth: 70),
|
||||
// 입출고일
|
||||
_buildHeaderCell('입출고일', flex: 2, useExpanded: useExpanded, minWidth: 80),
|
||||
// 관리
|
||||
_buildHeaderCell('관리', flex: 2, useExpanded: useExpanded, minWidth: 90),
|
||||
],
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(color: Colors.black),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 체크박스
|
||||
_buildDataCell(
|
||||
ShadCheckbox(
|
||||
value: _selectedItems.contains(equipment.equipment.id ?? 0),
|
||||
onChanged: (bool? value) {
|
||||
if (equipment.equipment.id != null) {
|
||||
_onItemSelected(equipment.equipment.id!, value ?? false);
|
||||
}
|
||||
},
|
||||
),
|
||||
flex: 1,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 40,
|
||||
),
|
||||
// 번호
|
||||
_buildDataCell(
|
||||
Text(
|
||||
'${((_controller.currentPage - 1) * _controller.pageSize) + index + 1}',
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
flex: 1,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 50,
|
||||
),
|
||||
// 제조사
|
||||
_buildDataCell(
|
||||
_buildTextWithTooltip(
|
||||
equipment.equipment.model?.vendor?.name ?? 'N/A',
|
||||
equipment.equipment.model?.vendor?.name ?? 'N/A',
|
||||
),
|
||||
flex: 3,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 120,
|
||||
),
|
||||
// 장비번호
|
||||
_buildDataCell(
|
||||
_buildTextWithTooltip(
|
||||
equipment.equipment.serialNumber ?? '',
|
||||
equipment.equipment.serialNumber ?? '',
|
||||
),
|
||||
flex: 3,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 120,
|
||||
),
|
||||
// 모델명
|
||||
_buildDataCell(
|
||||
_buildTextWithTooltip(
|
||||
equipment.equipment.model?.name ?? '-',
|
||||
equipment.equipment.model?.name ?? '-',
|
||||
),
|
||||
flex: 3,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 120,
|
||||
),
|
||||
// 상세 정보 (조건부) - 바코드로 변경
|
||||
if (_showDetailedColumns) ...[
|
||||
_buildDataCell(
|
||||
_buildTextWithTooltip(
|
||||
equipment.equipment.barcode ?? '-',
|
||||
equipment.equipment.barcode ?? '-',
|
||||
),
|
||||
flex: 3,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 120,
|
||||
),
|
||||
],
|
||||
// 수량 (백엔드에서 관리하지 않으므로 고정값)
|
||||
_buildDataCell(
|
||||
Text(
|
||||
'1',
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
flex: 1,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 50,
|
||||
),
|
||||
// 재고 상태
|
||||
_buildDataCell(
|
||||
_buildInventoryStatus(equipment),
|
||||
flex: 2,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 80,
|
||||
),
|
||||
// 상태
|
||||
_buildDataCell(
|
||||
_buildStatusBadge(equipment.status),
|
||||
flex: 2,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 70,
|
||||
),
|
||||
// 입출고일
|
||||
_buildDataCell(
|
||||
_buildCreatedDateWidget(equipment),
|
||||
flex: 2,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 80,
|
||||
),
|
||||
// 관리
|
||||
_buildDataCell(
|
||||
_buildActionButtons(equipment.equipment.id ?? 0),
|
||||
flex: 2,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 90,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 테이블 데이터
|
||||
...pagedEquipments.asMap().entries.map((entry) {
|
||||
final int index = entry.key;
|
||||
final UnifiedEquipment equipment = entry.value;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: ShadcnTheme.spacing4,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(color: Colors.black),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 체크박스
|
||||
_buildDataCell(
|
||||
Checkbox(
|
||||
value: _selectedItems.contains(equipment.equipment.id ?? 0),
|
||||
onChanged: (bool? value) {
|
||||
if (equipment.equipment.id != null) {
|
||||
_onItemSelected(equipment.equipment.id!, value ?? false);
|
||||
}
|
||||
},
|
||||
),
|
||||
flex: 1,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 40,
|
||||
),
|
||||
// 번호
|
||||
_buildDataCell(
|
||||
Text(
|
||||
'${((_controller.currentPage - 1) * _controller.pageSize) + index + 1}',
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
flex: 1,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 50,
|
||||
),
|
||||
// 제조사
|
||||
_buildDataCell(
|
||||
_buildTextWithTooltip(
|
||||
equipment.equipment.manufacturer,
|
||||
equipment.equipment.manufacturer,
|
||||
),
|
||||
flex: 3,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 120,
|
||||
),
|
||||
// 장비번호
|
||||
_buildDataCell(
|
||||
_buildTextWithTooltip(
|
||||
equipment.equipment.equipmentNumber, // name → equipmentNumber (메인 필드)
|
||||
equipment.equipment.equipmentNumber,
|
||||
),
|
||||
flex: 3,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 120,
|
||||
),
|
||||
// 모델명
|
||||
_buildDataCell(
|
||||
_buildTextWithTooltip(
|
||||
equipment.equipment.modelName ?? '-', // 모델명 표시
|
||||
equipment.equipment.modelName ?? '-',
|
||||
),
|
||||
flex: 3,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 120,
|
||||
),
|
||||
// 상세 정보 (조건부)
|
||||
if (_showDetailedColumns) ...[
|
||||
_buildDataCell(
|
||||
_buildTextWithTooltip(
|
||||
equipment.equipment.serialNumber ?? '-',
|
||||
equipment.equipment.serialNumber ?? '-',
|
||||
),
|
||||
flex: 3,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 120,
|
||||
),
|
||||
],
|
||||
// 수량
|
||||
_buildDataCell(
|
||||
Text(
|
||||
equipment.equipment.quantity.toString(),
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
flex: 1,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 50,
|
||||
),
|
||||
// 상태
|
||||
_buildDataCell(
|
||||
_buildStatusBadge(equipment.status),
|
||||
flex: 2,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 70,
|
||||
),
|
||||
// 입출고일
|
||||
_buildDataCell(
|
||||
_buildCreatedDateWidget(equipment),
|
||||
flex: 2,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 80,
|
||||
),
|
||||
// 관리
|
||||
_buildDataCell(
|
||||
_buildActionButtons(equipment.equipment.id ?? 0),
|
||||
flex: 2,
|
||||
useExpanded: useExpanded,
|
||||
minWidth: 90,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1051,6 +1063,60 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 재고 상태 위젯 빌더 (백엔드 기반 단순화)
|
||||
Widget _buildInventoryStatus(UnifiedEquipment equipment) {
|
||||
// 백엔드 Equipment_History 기반으로 단순 상태만 표시
|
||||
Widget stockInfo;
|
||||
if (equipment.status == EquipmentStatus.in_) {
|
||||
// 입고 상태: 재고 있음
|
||||
stockInfo = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.check_circle, color: Colors.green, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'보유중',
|
||||
style: ShadcnTheme.bodySmall.copyWith(color: Colors.green[700]),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (equipment.status == EquipmentStatus.out) {
|
||||
// 출고 상태: 재고 없음
|
||||
stockInfo = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.warning, color: Colors.orange, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'출고됨',
|
||||
style: ShadcnTheme.bodySmall.copyWith(color: Colors.orange[700]),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (equipment.status == EquipmentStatus.rent) {
|
||||
// 대여 상태
|
||||
stockInfo = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.schedule, color: Colors.blue, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'대여중',
|
||||
style: ShadcnTheme.bodySmall.copyWith(color: Colors.blue[700]),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
// 기타 상태
|
||||
stockInfo = Text(
|
||||
'-',
|
||||
style: ShadcnTheme.bodySmall,
|
||||
);
|
||||
}
|
||||
|
||||
return stockInfo;
|
||||
}
|
||||
|
||||
/// 상태 배지 빌더
|
||||
Widget _buildStatusBadge(String status) {
|
||||
String displayText;
|
||||
@@ -1148,7 +1214,7 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
final result = await EquipmentHistoryDialog.show(
|
||||
context: context,
|
||||
equipmentId: equipmentId,
|
||||
equipmentName: '${equipment.equipment.manufacturer} ${equipment.equipment.equipmentNumber}', // name → equipmentNumber
|
||||
equipmentName: '${equipment.equipment.model?.vendor?.name ?? 'N/A'} ${equipment.equipment.serialNumber}', // Vendor + Equipment Number
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
@@ -1156,15 +1222,6 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
}
|
||||
}
|
||||
|
||||
// 편집 다이얼로그 표시
|
||||
void _showEditDialog(UnifiedEquipment equipment) {
|
||||
_handleEdit(equipment);
|
||||
}
|
||||
|
||||
// 삭제 다이얼로그 표시
|
||||
void _showDeleteDialog(UnifiedEquipment equipment) {
|
||||
_handleDelete(equipment);
|
||||
}
|
||||
|
||||
// 편집 핸들러 (액션 버튼에서 호출) - 장비 ID로 처리
|
||||
void _handleEditById(int equipmentId) {
|
||||
@@ -1197,27 +1254,45 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
});
|
||||
}
|
||||
|
||||
/// 페이지 데이터 가져오기
|
||||
List<UnifiedEquipment> _getPagedEquipments() {
|
||||
// 서버 페이지네이션 사용: 컨트롤러의 items가 이미 페이지네이션된 데이터
|
||||
// 로컬 필터링만 적용
|
||||
return _getFilteredEquipments();
|
||||
}
|
||||
|
||||
// 사용하지 않는 카테고리 관련 함수들 제거됨 (리스트 API에서 제공하지 않음)
|
||||
|
||||
/// 캐시된 데이터를 사용한 상태 드롭다운 아이템 생성
|
||||
List<DropdownMenuItem<String>> _buildStatusDropdownItems() {
|
||||
List<DropdownMenuItem<String>> items = [
|
||||
const DropdownMenuItem(value: 'all', child: Text('전체')),
|
||||
/// 상태 표시 텍스트 가져오기
|
||||
String _getStatusDisplayText(String status) {
|
||||
switch (status) {
|
||||
case 'all':
|
||||
return '전체';
|
||||
case 'in':
|
||||
return '입고';
|
||||
case 'out':
|
||||
return '출고';
|
||||
case 'rent':
|
||||
return '대여';
|
||||
case 'repair':
|
||||
return '수리중';
|
||||
case 'damaged':
|
||||
return '손상';
|
||||
case 'lost':
|
||||
return '분실';
|
||||
case 'disposed':
|
||||
return '폐기';
|
||||
default:
|
||||
return '전체';
|
||||
}
|
||||
}
|
||||
|
||||
/// 캐시된 데이터를 사용한 상태 선택 옵션 생성
|
||||
List<ShadOption<String>> _buildStatusSelectOptions() {
|
||||
List<ShadOption<String>> options = [
|
||||
const ShadOption(value: 'all', child: Text('전체')),
|
||||
];
|
||||
|
||||
// 캐시된 상태 데이터에서 드롭다운 아이템 생성
|
||||
// 캐시된 상태 데이터에서 선택 옵션 생성
|
||||
final cachedStatuses = _controller.getCachedEquipmentStatuses();
|
||||
|
||||
for (final status in cachedStatuses) {
|
||||
items.add(
|
||||
DropdownMenuItem(
|
||||
options.add(
|
||||
ShadOption(
|
||||
value: status.id,
|
||||
child: Text(status.name),
|
||||
),
|
||||
@@ -1226,18 +1301,18 @@ class _EquipmentListState extends State<EquipmentList> {
|
||||
|
||||
// 캐시된 데이터가 없을 때 폴백으로 하드코딩된 상태 사용
|
||||
if (cachedStatuses.isEmpty) {
|
||||
items.addAll([
|
||||
const DropdownMenuItem(value: 'in', child: Text('입고')),
|
||||
const DropdownMenuItem(value: 'out', child: Text('출고')),
|
||||
const DropdownMenuItem(value: 'rent', child: Text('대여')),
|
||||
const DropdownMenuItem(value: 'repair', child: Text('수리중')),
|
||||
const DropdownMenuItem(value: 'damaged', child: Text('손상')),
|
||||
const DropdownMenuItem(value: 'lost', child: Text('분실')),
|
||||
const DropdownMenuItem(value: 'disposed', child: Text('폐기')),
|
||||
options.addAll([
|
||||
const ShadOption(value: 'in', child: Text('입고')),
|
||||
const ShadOption(value: 'out', child: Text('출고')),
|
||||
const ShadOption(value: 'rent', child: Text('대여')),
|
||||
const ShadOption(value: 'repair', child: Text('수리중')),
|
||||
const ShadOption(value: 'damaged', child: Text('손상')),
|
||||
const ShadOption(value: 'lost', child: Text('분실')),
|
||||
const ShadOption(value: 'disposed', child: Text('폐기')),
|
||||
]);
|
||||
}
|
||||
|
||||
return items;
|
||||
return options;
|
||||
}
|
||||
|
||||
// 사용하지 않는 현재위치, 점검일 관련 함수들 제거됨 (리스트 API에서 제공하지 않음)
|
||||
|
||||
Reference in New Issue
Block a user