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/common/widgets/pagination.dart'; import 'package:superport/screens/common/widgets/unified_search_bar.dart'; import 'package:superport/screens/common/widgets/standard_action_bar.dart'; import 'package:superport/screens/common/widgets/standard_data_table.dart' as std_table; import 'package:superport/screens/common/widgets/standard_states.dart'; import 'package:superport/screens/equipment/controllers/equipment_list_controller.dart'; import 'package:superport/services/mock_data_service.dart'; import 'package:superport/models/equipment_unified_model.dart'; import 'package:superport/utils/constants.dart'; import 'package:superport/utils/equipment_display_helper.dart'; /// shadcn/ui 스타일로 재설계된 장비 관리 화면 class EquipmentListRedesign extends StatefulWidget { final String currentRoute; const EquipmentListRedesign({Key? key, this.currentRoute = Routes.equipment}) : super(key: key); @override State createState() => _EquipmentListRedesignState(); } class _EquipmentListRedesignState extends State { late final EquipmentListController _controller; bool _showDetailedColumns = true; final TextEditingController _searchController = TextEditingController(); final ScrollController _horizontalScrollController = ScrollController(); String _selectedStatus = 'all'; // String _searchKeyword = ''; // Removed - unused field String _appliedSearchKeyword = ''; int _currentPage = 1; final int _pageSize = 10; @override void initState() { super.initState(); _controller = EquipmentListController(dataService: MockDataService()); _setInitialFilter(); // API 호출을 위해 Future로 변경 WidgetsBinding.instance.addPostFrameCallback((_) { _controller.loadData(); // 비동기 호출 }); } @override void dispose() { _searchController.dispose(); _horizontalScrollController.dispose(); _controller.dispose(); super.dispose(); } @override void didChangeDependencies() { super.didChangeDependencies(); _adjustColumnsForScreenSize(); } /// 화면 크기에 따라 컬럼 표시 조정 void _adjustColumnsForScreenSize() { final width = MediaQuery.of(context).size.width; setState(() { _showDetailedColumns = width > 900; }); } /// 라우트에 따른 초기 필터 설정 void _setInitialFilter() { switch (widget.currentRoute) { case Routes.equipmentInList: _selectedStatus = 'in'; _controller.selectedStatusFilter = EquipmentStatus.in_; break; case Routes.equipmentOutList: _selectedStatus = 'out'; _controller.selectedStatusFilter = EquipmentStatus.out; break; case Routes.equipmentRentList: _selectedStatus = 'rent'; _controller.selectedStatusFilter = EquipmentStatus.rent; break; default: _selectedStatus = 'all'; _controller.selectedStatusFilter = null; } print('DEBUG: Initial filter set - route: ${widget.currentRoute}, status: $_selectedStatus, filter: ${_controller.selectedStatusFilter}'); // 디버그 정보 } /// 데이터 로드 Future _loadData({bool isRefresh = false}) async { await _controller.loadData(isRefresh: isRefresh); } /// 상태 필터 변경 Future _onStatusFilterChanged(String status) async { setState(() { _selectedStatus = status; // 상태 필터를 EquipmentStatus 상수로 변환 if (status == 'all') { _controller.selectedStatusFilter = null; } else if (status == 'in') { _controller.selectedStatusFilter = EquipmentStatus.in_; } else if (status == 'out') { _controller.selectedStatusFilter = EquipmentStatus.out; } else if (status == 'rent') { _controller.selectedStatusFilter = EquipmentStatus.rent; } _currentPage = 1; }); await _controller.changeStatusFilter(_controller.selectedStatusFilter); } /// 검색 실행 void _onSearch() async { setState(() { _appliedSearchKeyword = _searchController.text; _currentPage = 1; }); await _controller.updateSearchKeyword(_searchController.text); } /// 장비 선택/해제 void _onEquipmentSelected(int? id, String status, bool? isSelected) { setState(() { _controller.selectEquipment(id, status, isSelected); }); } /// 전체 선택/해제 void _onSelectAll(bool? value) { setState(() { final equipments = _getFilteredEquipments(); for (final equipment in equipments) { _controller.selectEquipment(equipment.id, equipment.status, value); } }); } /// 전체 선택 상태 확인 bool _isAllSelected() { final equipments = _getFilteredEquipments(); if (equipments.isEmpty) return false; return equipments.every((e) => _controller.selectedEquipmentIds.contains('${e.id}:${e.status}')); } /// 필터링된 장비 목록 반환 List _getFilteredEquipments() { var equipments = _controller.equipments; print('DEBUG: Total equipments from controller: ${equipments.length}'); // 디버그 정보 // 검색 키워드 적용 (확장된 검색 필드) if (_appliedSearchKeyword.isNotEmpty) { equipments = 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(); } print('DEBUG: Filtered equipments count: ${equipments.length}'); // 디버그 정보 print('DEBUG: Selected status filter: $_selectedStatus'); // 디버그 정보 return equipments; } /// 출고 처리 버튼 핸들러 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), ), ); }), ], ), ), 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('폐기'), ), ], ), ); } /// 편집 핸들러 void _handleEdit(UnifiedEquipment equipment) async { if (equipment.status == EquipmentStatus.in_) { final result = await Navigator.pushNamed( context, Routes.equipmentInEdit, arguments: equipment.id, ); if (result == true) { setState(() { _controller.loadData(); }); } } else { // 출고/대여 등은 별도 폼으로 이동 필요시 구현 ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('해당 상태의 편집 기능은 준비 중입니다.')), ); } } /// 삭제 핸들러 void _handleDelete(UnifiedEquipment equipment) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('삭제 확인'), content: const Text('이 장비 정보를 삭제하시겠습니까?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('취소'), ), TextButton( onPressed: () async { Navigator.pop(context); // 로딩 다이얼로그 표시 showDialog( context: context, barrierDismissible: false, builder: (context) => const Center( child: CircularProgressIndicator(), ), ); // Controller를 통한 삭제 처리 final success = await _controller.deleteEquipment(equipment); // 로딩 다이얼로그 닫기 if (mounted) Navigator.pop(context); if (success) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('장비가 삭제되었습니다.')), ); } } else { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(_controller.error ?? '삭제 중 오류가 발생했습니다.'), backgroundColor: Colors.red, ), ); } } }, child: const Text('삭제', style: TextStyle(color: Colors.red)), ), ], ), ); } /// 이력 보기 핸들러 void _handleHistory(UnifiedEquipment equipment) async { if (equipment.equipment.id == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('장비 ID가 없습니다.')), ); return; } final result = await Navigator.pushNamed( context, Routes.equipmentHistory, arguments: { 'equipmentId': equipment.equipment.id, 'equipmentName': '${equipment.equipment.manufacturer} ${equipment.equipment.name}', }, ); if (result == true) { _controller.loadData(isRefresh: true); } } @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: _controller, child: Consumer( 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), // 장비 테이블 Expanded( child: controller.isLoading ? _buildLoadingState() : controller.error != null ? _buildErrorState() : _buildEquipmentTable(), ), ], ), ); }, ), ); } /// 필터 바 Widget _buildFilterBar(int selectedCount, int selectedInCount, int selectedOutCount, int selectedRentCount) { return Container( padding: const EdgeInsets.all(24), child: Column( children: [ // 검색 및 필터 섹션 Row( children: [ // 검색 입력 Expanded( flex: 2, child: Container( height: 40, decoration: BoxDecoration( color: ShadcnTheme.card, borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), border: Border.all(color: Colors.black), ), child: TextField( controller: _searchController, onSubmitted: (_) => _onSearch(), decoration: InputDecoration( hintText: '장비명, 제조사, 카테고리, 시리얼번호 등...', hintStyle: TextStyle(color: ShadcnTheme.mutedForeground.withValues(alpha: 0.8), fontSize: 14), prefixIcon: Icon(Icons.search, color: ShadcnTheme.muted, size: 20), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), style: ShadcnTheme.bodyMedium, ), ), ), const SizedBox(width: 16), // 검색 버튼 SizedBox( height: 40, child: ShadcnButton( text: '검색', onPressed: _onSearch, variant: ShadcnButtonVariant.primary, textColor: Colors.white, icon: const Icon(Icons.search, size: 16), ), ), const SizedBox(width: 16), // 상태 필터 드롭다운 Container( 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( value: _selectedStatus, onChanged: (value) => _onStatusFilterChanged(value!), style: TextStyle(fontSize: 14, color: ShadcnTheme.foreground), icon: const Icon(Icons.arrow_drop_down, size: 20), items: const [ DropdownMenuItem(value: 'all', child: Text('전체')), DropdownMenuItem(value: 'in', child: Text('입고')), DropdownMenuItem(value: 'out', child: Text('출고')), DropdownMenuItem(value: 'rent', child: Text('대여')), ], ), ), ), ], ), const SizedBox(height: 16), // 액션 버튼들 및 상태 표시 Row( children: [ // 라우트별 액션 버튼 _buildRouteSpecificActions(selectedInCount, selectedOutCount, selectedRentCount), const Spacer(), // 선택 및 총 개수 표시 if (selectedCount > 0) Container( padding: const EdgeInsets.symmetric( vertical: 8, horizontal: 16, ), decoration: BoxDecoration( color: ShadcnTheme.muted.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(ShadcnTheme.radiusSm), ), child: Text( '$selectedCount개 선택됨', style: const TextStyle(fontWeight: FontWeight.bold), ), ), if (selectedCount > 0) const SizedBox(width: 12), Text( '총 ${_getFilteredEquipments().length}개', style: ShadcnTheme.bodyMuted, ), const SizedBox(width: 12), // 새로고침 버튼 IconButton( icon: const Icon(Icons.refresh), onPressed: () { setState(() { _controller.loadData(); _currentPage = 1; }); }, ), // 뷰 모드 전환 버튼 IconButton( icon: Icon(_showDetailedColumns ? Icons.view_column : Icons.view_compact), tooltip: _showDetailedColumns ? '간소화된 보기' : '상세 보기', onPressed: () { setState(() { _showDetailedColumns = !_showDetailedColumns; }); }, ), ], ), ], ), ); } /// 라우트별 액션 버튼 Widget _buildRouteSpecificActions(int selectedInCount, int selectedOutCount, int selectedRentCount) { switch (widget.currentRoute) { case Routes.equipmentInList: return Row( children: [ ShadcnButton( text: '출고', onPressed: selectedInCount > 0 ? _handleOutEquipment : null, variant: selectedInCount > 0 ? ShadcnButtonVariant.primary : ShadcnButtonVariant.secondary, icon: const Icon(Icons.exit_to_app, size: 16), ), const SizedBox(width: 8), ShadcnButton( text: '입고', onPressed: () async { final result = await Navigator.pushNamed( context, Routes.equipmentInAdd, ); if (result == true) { setState(() { _controller.loadData(); _currentPage = 1; }); } }, variant: ShadcnButtonVariant.primary, textColor: Colors.white, icon: const Icon(Icons.add, size: 16), ), ], ); case Routes.equipmentOutList: return Row( children: [ ShadcnButton( text: '재입고', onPressed: selectedOutCount > 0 ? () => ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('재입고 기능은 준비 중입니다.')), ) : null, variant: selectedOutCount > 0 ? ShadcnButtonVariant.primary : ShadcnButtonVariant.secondary, icon: const Icon(Icons.assignment_return, size: 16), ), const SizedBox(width: 8), ShadcnButton( text: '수리 요청', onPressed: selectedOutCount > 0 ? () => ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('수리 요청 기능은 준비 중입니다.')), ) : null, variant: selectedOutCount > 0 ? ShadcnButtonVariant.destructive : ShadcnButtonVariant.secondary, icon: const Icon(Icons.build, size: 16), ), ], ); case Routes.equipmentRentList: return Row( children: [ ShadcnButton( text: '반납', onPressed: selectedRentCount > 0 ? () => ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('대여 반납 기능은 준비 중입니다.')), ) : null, variant: selectedRentCount > 0 ? ShadcnButtonVariant.primary : ShadcnButtonVariant.secondary, icon: const Icon(Icons.keyboard_return, size: 16), ), const SizedBox(width: 8), ShadcnButton( text: '연장', onPressed: selectedRentCount > 0 ? () => ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('대여 연장 기능은 준비 중입니다.')), ) : null, variant: selectedRentCount > 0 ? ShadcnButtonVariant.primary : ShadcnButtonVariant.secondary, icon: const Icon(Icons.date_range, size: 16), ), ], ); default: return Row( children: [ ShadcnButton( text: '입고', onPressed: () async { final result = await Navigator.pushNamed( context, Routes.equipmentInAdd, ); if (result == true) { setState(() { _controller.loadData(); _currentPage = 1; }); } }, variant: ShadcnButtonVariant.primary, textColor: Colors.white, icon: const Icon(Icons.add, size: 16), ), const SizedBox(width: 8), ShadcnButton( text: '출고 처리', onPressed: selectedInCount > 0 ? _handleOutEquipment : null, variant: selectedInCount > 0 ? ShadcnButtonVariant.primary : ShadcnButtonVariant.secondary, textColor: selectedInCount > 0 ? Colors.white : null, icon: const Icon(Icons.local_shipping, size: 16), ), const SizedBox(width: 8), ShadcnButton( text: '대여 처리', onPressed: selectedInCount > 0 ? _handleRentEquipment : null, variant: selectedInCount > 0 ? ShadcnButtonVariant.secondary : ShadcnButtonVariant.secondary, icon: const Icon(Icons.assignment, size: 16), ), const SizedBox(width: 8), ShadcnButton( text: '폐기 처리', onPressed: selectedInCount > 0 ? _handleDisposeEquipment : null, variant: selectedInCount > 0 ? ShadcnButtonVariant.destructive : ShadcnButtonVariant.secondary, icon: const Icon(Icons.delete, size: 16), ), ], ); } } /// 로딩 상태 Widget _buildLoadingState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(color: ShadcnTheme.primary), const SizedBox(height: 16), Text('데이터를 불러오는 중...', style: ShadcnTheme.bodyMuted), ], ), ); } /// 에러 상태 위젯 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 pagedEquipments) { double totalWidth = 0; // 기본 컬럼들 (너비 최적화) totalWidth += 40; // 체크박스 totalWidth += 50; // 번호 totalWidth += 120; // 제조사 totalWidth += 120; // 장비명 totalWidth += 100; // 카테고리 totalWidth += 50; // 수량 totalWidth += 70; // 상태 totalWidth += 80; // 날짜 totalWidth += 90; // 관리 // 상세 컬럼들 (조건부) if (_showDetailedColumns) { totalWidth += 120; // 시리얼번호 totalWidth += 120; // 바코드 // 출고 정보 (조건부) if (pagedEquipments.any((e) => e.status == EquipmentStatus.out || e.status == EquipmentStatus.rent)) { totalWidth += 120; // 회사 totalWidth += 80; // 담당자 } } return totalWidth; } /// 장비 테이블 Widget _buildEquipmentTable() { final filteredEquipments = _getFilteredEquipments(); final totalCount = filteredEquipments.length; final int startIndex = (_currentPage - 1) * _pageSize; final int endIndex = (startIndex + _pageSize) > totalCount ? totalCount : (startIndex + _pageSize); final List pagedEquipments = filteredEquipments.sublist( startIndex, endIndex, ); return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( children: [ // 테이블 컨테이너 (가로 스크롤 지원) SingleChildScrollView( scrollDirection: Axis.horizontal, controller: _horizontalScrollController, child: Container( constraints: BoxConstraints( minWidth: MediaQuery.of(context).size.width - 48, // padding 고려 ), decoration: BoxDecoration( border: Border.all(color: Colors.black), borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), ), child: pagedEquipments.isEmpty ? Container( padding: const EdgeInsets.all(64), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.inventory_2_outlined, size: 48, color: ShadcnTheme.mutedForeground, ), const SizedBox(height: 16), Text('장비가 없습니다', style: ShadcnTheme.bodyMuted), ], ), ), ) : SizedBox( width: _calculateTableWidth(pagedEquipments), child: 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: [ // 체크박스 SizedBox( width: 40, child: Checkbox( value: _isAllSelected(), onChanged: _onSelectAll, ), ), // 번호 SizedBox( width: 50, child: Text('번호', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), // 제조사 SizedBox( width: 120, child: Text('제조사', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), // 장비명 SizedBox( width: 120, child: Text('장비명', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), // 카테고리 SizedBox( width: 100, child: Text('카테고리', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), // 상세 정보 (조건부) if (_showDetailedColumns) ...[ SizedBox( width: 120, child: Text('시리얼번호', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), SizedBox( width: 120, child: Text('바코드', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), ], // 수량 SizedBox( width: 50, child: Text('수량', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), // 상태 SizedBox( width: 70, child: Text('상태', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), // 날짜 SizedBox( width: 80, child: Text('날짜', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), // 출고 정보 (조건부 - 테이블에 출고/대여 항목이 있을 때만) if (_showDetailedColumns && pagedEquipments.any((e) => e.status == EquipmentStatus.out || e.status == EquipmentStatus.rent)) ...[ SizedBox( width: 120, child: Text('회사', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), SizedBox( width: 80, child: Text('담당자', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), ], // 관리 SizedBox( width: 90, child: Text('관리', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ), ], ), ), // 테이블 데이터 ...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: [ // 체크박스 SizedBox( width: 40, child: Checkbox( value: _controller.selectedEquipmentIds.contains('${equipment.id}:${equipment.status}'), onChanged: (value) => _onEquipmentSelected(equipment.id, equipment.status, value), ), ), // 번호 SizedBox( width: 50, child: Text( '${startIndex + index + 1}', style: ShadcnTheme.bodySmall, ), ), // 제조사 SizedBox( width: 120, child: Text( equipment.equipment.manufacturer, style: ShadcnTheme.bodySmall, overflow: TextOverflow.ellipsis, ), ), // 장비명 SizedBox( width: 120, child: Text( equipment.equipment.name, style: ShadcnTheme.bodySmall, overflow: TextOverflow.ellipsis, ), ), // 카테고리 SizedBox( width: 100, child: _buildCategoryWithTooltip(equipment), ), // 상세 정보 (조건부) if (_showDetailedColumns) ...[ SizedBox( width: 120, child: Text( equipment.equipment.serialNumber ?? '-', style: ShadcnTheme.bodySmall, overflow: TextOverflow.ellipsis, ), ), SizedBox( width: 120, child: Text( equipment.equipment.barcode ?? '-', style: ShadcnTheme.bodySmall, overflow: TextOverflow.ellipsis, ), ), ], // 수량 SizedBox( width: 50, child: Text( '${equipment.equipment.quantity}', style: ShadcnTheme.bodySmall, ), ), // 상태 SizedBox( width: 70, child: ShadcnBadge( text: _getStatusDisplayText( equipment.status, ), variant: _getStatusBadgeVariant( equipment.status, ), size: ShadcnBadgeSize.small, ), ), // 날짜 SizedBox( width: 80, child: Text( equipment.date.toString().substring(0, 10), style: ShadcnTheme.bodySmall, ), ), // 출고 정보 (조건부) if (_showDetailedColumns && pagedEquipments.any((e) => e.status == EquipmentStatus.out || e.status == EquipmentStatus.rent)) ...[ SizedBox( width: 120, child: Text( equipment.status == EquipmentStatus.out || equipment.status == EquipmentStatus.rent ? _controller.getOutEquipmentInfo(equipment.id!, 'company') : '-', style: ShadcnTheme.bodySmall, overflow: TextOverflow.ellipsis, ), ), SizedBox( width: 80, child: Text( equipment.status == EquipmentStatus.out || equipment.status == EquipmentStatus.rent ? _controller.getOutEquipmentInfo(equipment.id!, 'manager') : '-', style: ShadcnTheme.bodySmall, overflow: TextOverflow.ellipsis, ), ), ], // 관리 버튼 SizedBox( width: 90, child: Row( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: IconButton( constraints: const BoxConstraints( minWidth: 30, minHeight: 30, ), padding: const EdgeInsets.all(4), icon: const Icon(Icons.history, size: 16), onPressed: () => _handleHistory(equipment), tooltip: '이력', ), ), Flexible( child: IconButton( constraints: const BoxConstraints( minWidth: 30, minHeight: 30, ), padding: const EdgeInsets.all(4), icon: const Icon(Icons.edit_outlined, size: 16), onPressed: () => _handleEdit(equipment), tooltip: '편집', ), ), Flexible( child: IconButton( constraints: const BoxConstraints( minWidth: 30, minHeight: 30, ), padding: const EdgeInsets.all(4), icon: const Icon(Icons.delete_outline, size: 16), onPressed: () => _handleDelete(equipment), tooltip: '삭제', ), ), ], ), ), ], ), ); }), ], ), ), ), ), // 페이지네이션 컴포넌트 if (totalCount > _pageSize) Pagination( totalCount: totalCount, currentPage: _currentPage, pageSize: _pageSize, onPageChanged: (page) { setState(() { _currentPage = page; }); }, ), ], ), ); } /// 상태 표시 텍스트 반환 String _getStatusDisplayText(String status) { switch (status) { case 'I': // EquipmentStatus.in_ return '입고'; case 'O': // EquipmentStatus.out return '출고'; case 'T': // EquipmentStatus.rent return '대여'; default: return '알수없음'; } } /// 상태에 따른 배지 변형 반환 ShadcnBadgeVariant _getStatusBadgeVariant(String status) { switch (status) { case 'I': // EquipmentStatus.in_ return ShadcnBadgeVariant.success; case 'O': // EquipmentStatus.out return ShadcnBadgeVariant.destructive; case 'T': // EquipmentStatus.rent return ShadcnBadgeVariant.warning; default: return ShadcnBadgeVariant.secondary; } } /// 카테고리 축약 표기 함수 String _shortenCategory(String category) { if (category.length <= 2) return category; return '${category.substring(0, 2)}...'; } /// 카테고리 툴팁 위젯 Widget _buildCategoryWithTooltip(UnifiedEquipment equipment) { final fullCategory = EquipmentDisplayHelper.formatCategory( equipment.equipment.category, equipment.equipment.subCategory, equipment.equipment.subSubCategory, ); // 축약 표기 적용 - 비어있지 않은 카테고리만 표시 final List parts = []; if (equipment.equipment.category.isNotEmpty) { parts.add(_shortenCategory(equipment.equipment.category)); } if (equipment.equipment.subCategory.isNotEmpty) { parts.add(_shortenCategory(equipment.equipment.subCategory)); } if (equipment.equipment.subSubCategory.isNotEmpty) { parts.add(_shortenCategory(equipment.equipment.subSubCategory)); } final shortCategory = parts.join(' > '); return Tooltip( message: fullCategory, child: Text( shortCategory, style: ShadcnTheme.bodySmall, overflow: TextOverflow.ellipsis, ), ); } }