사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)

This commit is contained in:
JiWoong Sul
2025-08-29 15:11:59 +09:00
parent a740ff10c8
commit d916b281a7
333 changed files with 53617 additions and 22574 deletions

View File

@@ -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에서 제공하지 않음)