import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:inventory_system/services/item_service.dart'; import 'package:inventory_system/screens/admin/item/item_form.dart'; import 'package:inventory_system/screens/bottom_nav_bar.dart'; import 'package:inventory_system/screens/nav_bar.dart'; import 'package:inventory_system/screens/title_bar.dart'; import 'package:qr_flutter/qr_flutter.dart'; class ItemScreen extends StatefulWidget { const ItemScreen({super.key}); @override State createState() => _ItemScreenState(); } class _ItemScreenState extends State { int _selectedIndex = 0; final ItemService _itemService = ItemService(); late Future> _itemsFuture; String _selectedFilter = 'All'; final List _filters = ['All', 'Asset', 'Disposable', 'Part']; List _allItems = []; List _filteredItems = []; final TextEditingController _searchController = TextEditingController(); int? _expandedItemId; @override void initState() { super.initState(); _itemsFuture = _fetchItems(); _searchController.addListener(_filterAndSortItems); } Future> _fetchItems() async { try { final items = await _itemService.fetchItems(); if (mounted) { setState(() { _allItems = items; _filterAndSortItems(); }); } return items; } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to load items: $e'), backgroundColor: Colors.red), ); } rethrow; } } @override void dispose() { _searchController.removeListener(_filterAndSortItems); _searchController.dispose(); super.dispose(); } void _filterAndSortItems() { final query = _searchController.text.toLowerCase(); final filtered = _allItems.where((item) { final name = item['productName']?.toString().toLowerCase() ?? ''; final serial = item['serialNumber']?.toString().toLowerCase() ?? ''; final category = item['category']?.toString().toLowerCase() ?? ''; final uniqueId = item['uniqueID']?.toString().toLowerCase() ?? ''; final matchesSearch = name.contains(query) || serial.contains(query) || uniqueId.contains(query); final matchesFilter = _selectedFilter == 'All' || category == _selectedFilter.toLowerCase(); return matchesSearch && matchesFilter; }).toList(); // Sort the filtered list by uniqueID filtered.sort((a, b) { final idA = a['uniqueID'] as String? ?? ''; final idB = b['uniqueID'] as String? ?? ''; return idA.compareTo(idB); }); setState(() { _filteredItems = filtered; _expandedItemId = null; }); } void _deleteItem(int itemId) async { try { await _itemService.deleteItem(itemId); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Item deleted successfully'), backgroundColor: Colors.green), ); _fetchItems(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to delete item: $e'), backgroundColor: Colors.red), ); } } } void _confirmDelete(int itemId, String itemName) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Delete Item'), content: Text('Are you sure you want to delete $itemName?'), actions: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text('Cancel')), TextButton( onPressed: () { Navigator.pop(context); _deleteItem(itemId); }, child: const Text('Delete', style: TextStyle(color: Colors.red)), ), ], ), ); } void _showPrintDialog(Map item) { showDialog( context: context, builder: (context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Stack( clipBehavior: Clip.none, children: [ Padding( padding: const EdgeInsets.fromLTRB(24, 48, 24, 24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Column( children: [ QrImageView( data: item['uniqueID'] ?? '', version: QrVersions.auto, size: 100.0, ), const SizedBox(height: 8), Text( item['uniqueID'] ?? '', style: const TextStyle(fontFamily: 'monospace', fontSize: 12), ), ], ), const SizedBox(width: 24), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item['currentStore'] ?? 'N/A', style: const TextStyle(fontFamily: 'monospace', fontSize: 16)), const SizedBox(height: 8), Text(item['productShortName'] ?? item['productName'] ?? '', style: const TextStyle(fontFamily: 'monospace', fontSize: 16)), const SizedBox(height: 8), Text(item['serialNumber'] ?? '', style: const TextStyle(fontFamily: 'monospace', fontSize: 16)), const SizedBox(height: 8), Text(item['partNumber'] ?? '', style: const TextStyle(fontFamily: 'monospace', fontSize: 16)), ], ), ), ], ), const SizedBox(height: 24), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey.shade200, foregroundColor: Colors.black87, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8), side: BorderSide(color: Colors.grey.shade400)), ), child: const Text('Print QR Info'), ), ), ], ), ), Positioned( top: 8, right: 8, child: IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), ), ], ), ); }, ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F5F7), appBar: const TitleBar(title: 'Item'), drawer: const NavBar(isAdmin: true, selectedScreen: AppScreen.item), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Row( children: [ ElevatedButton.icon( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const ItemFormScreen()), ).then((success) { if (success == true) _fetchItems(); }); }, icon: const Icon(Icons.add, size: 18), label: const Text('Add Item'), style: ElevatedButton.styleFrom( backgroundColor: Colors.purple.shade100, foregroundColor: Colors.black87, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ), const SizedBox(width: 16), Expanded( child: TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Search', hintStyle: TextStyle(color: Colors.grey.shade400), prefixIcon: const Icon(Icons.search), filled: true, fillColor: Colors.white, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), ), ), ), ], ), const SizedBox(height: 16), SizedBox( height: 36, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: _filters.length, separatorBuilder: (context, index) => const SizedBox(width: 8), itemBuilder: (context, index) { final filter = _filters[index]; final isSelected = _selectedFilter == filter; return ChoiceChip( label: Text(filter), selected: isSelected, onSelected: (selected) { if (selected) { setState(() { _selectedFilter = filter; _filterAndSortItems(); }); } }, backgroundColor: Colors.white, selectedColor: Colors.purple.shade100, labelStyle: TextStyle(color: isSelected ? Colors.purple.shade900 : Colors.black54, fontWeight: FontWeight.bold), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18), side: BorderSide(color: isSelected ? Colors.purple.shade100 : Colors.grey.shade300)), ); }, ), ), const SizedBox(height: 16), Expanded( child: RefreshIndicator( onRefresh: _fetchItems, child: FutureBuilder>( future: _itemsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting && _allItems.isEmpty) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError && _allItems.isEmpty) { return Center(child: Text('Error: ${snapshot.error}')); } if (_filteredItems.isEmpty) { return const Center(child: Text('No items found.')); } return ListView.builder( itemCount: _filteredItems.length, itemBuilder: (context, index) { final item = _filteredItems[index]; return _buildItemWidget(item); }, ); }, ), ), ), ], ), ), bottomNavigationBar: BottomNavBar(selectedIndex: _selectedIndex, onItemTapped: (index) { if (index == 0) { Navigator.pop(context); } else if (index == 1) { if (mounted) { Navigator.pushNamed(context, '/scan'); } } else { setState(() { _selectedIndex = index; }); } }), ); } Widget _buildItemWidget(Map item) { final isExpanded = _expandedItemId == item['itemID']; return Padding( padding: const EdgeInsets.only(bottom: 12), child: Slidable( key: Key(item['itemID'].toString()), endActionPane: ActionPane( motion: const ScrollMotion(), extentRatio: 0.45, children: [ SlidableAction(onPressed: (context) => Navigator.push(context, MaterialPageRoute(builder: (context) => ItemFormScreen(item: item))).then((success) => {if(success == true) _fetchItems()}), backgroundColor: Colors.blue, foregroundColor: Colors.white, icon: Icons.edit, label: 'Edit'), SlidableAction(onPressed: (context) => _confirmDelete(item['itemID'], item['productName']), backgroundColor: Colors.red, foregroundColor: Colors.white, icon: Icons.delete, label: 'Delete'), ], ), child: Container( decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.grey.shade300, width: 1)), child: Column( children: [ Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ Container( width: 80, height: 80, padding: const EdgeInsets.all(4), decoration: BoxDecoration(border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8)), child: QrImageView(data: item['uniqueID'] ?? '', version: QrVersions.auto, size: 72), ), const SizedBox(height: 4), Text(item['uniqueID'] ?? '', style: TextStyle(fontSize: 12, color: Colors.black)), ], ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item['productName'] ?? 'N/A', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), const SizedBox(height: 8), Text('Serial Number: ${item['serialNumber'] ?? 'N/A'}', style: TextStyle(fontSize: 13, color: Colors.grey.shade600)), const SizedBox(height: 4), Text('Part Number: ${item['partNumber'] ?? 'N/A'}', style: TextStyle(fontSize: 13, color: Colors.grey.shade600)), ], ), ), ElevatedButton.icon(onPressed: () => _showPrintDialog(item), icon: const Icon(Icons.print, size: 16), label: const Text('Print'), style: ElevatedButton.styleFrom(backgroundColor: Colors.green.shade400, foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)))), ], ), ), InkWell( onTap: () => setState(() => _expandedItemId = isExpanded ? null : item['itemID']), child: Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey.shade200))), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(isExpanded ? 'Less Details' : 'More Details', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.blue.shade600)), const SizedBox(width: 4), Icon(isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, color: Colors.blue.shade600, size: 20), ], ), ), ), AnimatedSize( duration: const Duration(milliseconds: 300), curve: Curves.fastOutSlowIn, child: isExpanded ? Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.grey.shade50, border: Border(top: BorderSide(color: Colors.grey.shade200))), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: _buildDetailItem('Category', item['category'] ?? 'N/A')), const SizedBox(width: 16), Expanded(child: _buildDetailItem('Quantity', item['quantity'].toString())), const SizedBox(width: 16), Expanded(child: _buildDetailItem('Price', 'RM${item['convertPrice']?.toString() ?? '0.00'}'))]), const SizedBox(height: 12), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: _buildDetailItem('Supplier', item['supplier'] ?? 'N/A')), const SizedBox(width: 16), Expanded(child: _buildDetailItem('Purchase Date', item['purchaseDate'] ?? 'N/A')), const SizedBox(width: 16), Expanded(child: _buildDetailItem('Warranty Until', item['endWDate'] ?? 'N/A'))]), const SizedBox(height: 12), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: _buildDetailItem('Location', item['currentStation'] ?? 'N/A')), const SizedBox(width: 16), Expanded(child: _buildDetailItem('Store', item['currentStore'] ?? 'N/A')), const SizedBox(width: 16), Expanded(child: _buildDetailItem('User', item['currentUser'] ?? 'N/A')), ], ), ], ), ) : const SizedBox.shrink(), ), ], ), ), ), ); } Widget _buildDetailRow(String label, String value) => Row(crossAxisAlignment: CrossAxisAlignment.start, children: [SizedBox(width: 60, child: Text(label, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: Colors.grey.shade700))), Expanded(child: Text(value, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.black87)))]); Widget _buildDetailItem(String label, String value) { return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(label, style: TextStyle(fontSize: 12, color: Colors.grey.shade600, fontWeight: FontWeight.w500)), if (value.isNotEmpty) const SizedBox(height: 4), if (value.isNotEmpty) Text(value, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87))]); } }