Advertisement
Zuhairy_Harry

inventory.dart v1

Jun 23rd, 2025
202
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Dart 28.46 KB | None | 0 0
  1. import 'package:cloud_firestore/cloud_firestore.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:einventorycomputer/modules/home/screen/devices/device_details.dart';
  4.  
  5. class InventoryPage extends StatefulWidget {
  6.   const InventoryPage({super.key});
  7.  
  8.   @override
  9.   _InventoryPageState createState() => _InventoryPageState();
  10. }
  11.  
  12. class _InventoryPageState extends State<InventoryPage> {
  13.   String _searchQuery = '';
  14.   String _selectedType = 'All';
  15.   String _selectedFloor = 'All';
  16.   String _selectedBuilding = 'All';
  17.   String _selectedStatus = 'All';
  18.   List<String> _buildingOptions = ['All']; // Dynamic building list
  19.   bool _isBuildingDataLoaded = false;
  20.  
  21.   @override
  22.   void initState() {
  23.     super.initState();
  24.     _loadBuildingOptions();
  25.   }
  26.  
  27.   // Load building names from Firestore
  28.   Future<void> _loadBuildingOptions() async {
  29.     try {
  30.       final snapshot = await FirebaseFirestore.instance.collection('buildings').get();
  31.       final buildingNames = <String>['All'];
  32.      
  33.       for (var doc in snapshot.docs) {
  34.         final data = doc.data();
  35.         final name = data['name'];
  36.         if (name != null && name.toString().isNotEmpty) {
  37.           buildingNames.add(name.toString());
  38.         }
  39.       }
  40.      
  41.       setState(() {
  42.         _buildingOptions = buildingNames;
  43.         _isBuildingDataLoaded = true;
  44.       });
  45.     } catch (e) {
  46.       print('Error loading building options: $e');
  47.       setState(() {
  48.         _buildingOptions = ['All']; // Fallback to default
  49.         _isBuildingDataLoaded = true;
  50.       });
  51.     }
  52.   }
  53.  
  54.   Icon _getDeviceIcon(String? type) {
  55.     if (type == 'PC') return const Icon(Icons.computer_outlined, color: Color(0xFFFFC727), size: 20);
  56.     if (type == 'Peripheral') return const Icon(Icons.devices_other_outlined, color: Color(0xFFFFC727), size: 20);
  57.     return const Icon(Icons.device_unknown_outlined, color: Color(0xFFFFC727), size: 20);
  58.   }
  59.  
  60.   Color _getStatusColor(String status) {
  61.     switch (status.toLowerCase()) {
  62.       case 'online':
  63.         return const Color(0xFF4CAF50);
  64.       case 'offline':
  65.         return const Color(0xFFF44336);
  66.       default:
  67.         return const Color(0xFF9E9E9E);
  68.     }
  69.   }
  70.  
  71.   Future<Map<String, Map<String, String>>> _fetchLocationInfoMap() async {
  72.     final snapshot = await FirebaseFirestore.instance.collection('locations').get();
  73.     final map = <String, Map<String, String>>{};
  74.  
  75.     for (var doc in snapshot.docs) {
  76.       final data = doc.data();
  77.       final name = data['name'];
  78.       final floor = data['floor'];
  79.       final building = data['building'];
  80.       if (name != null && floor != null && building != null) {
  81.         map[name] = {'floor': floor, 'building': building};
  82.       }
  83.     }
  84.  
  85.     return map;
  86.   }
  87.  
  88.   @override
  89.   Widget build(BuildContext context) {
  90.     return Scaffold(
  91.       backgroundColor: const Color(0xFFF8F9FA),
  92.       appBar: AppBar(
  93.         backgroundColor: Colors.transparent,
  94.         elevation: 0,
  95.         iconTheme: const IconThemeData(color: Color(0xFF212529)),
  96.         bottom: PreferredSize(
  97.           preferredSize: const Size.fromHeight(100),
  98.           child: Padding(
  99.             padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
  100.             child: Column(
  101.               children: [
  102.                 // Search Bar - Made more compact
  103.                 Container(
  104.                   height: 40,
  105.                   decoration: BoxDecoration(
  106.                     color: Colors.white,
  107.                     borderRadius: BorderRadius.circular(12),
  108.                     boxShadow: [
  109.                       BoxShadow(
  110.                         color: Colors.black.withOpacity(0.05),
  111.                         blurRadius: 8,
  112.                         offset: const Offset(0, 2),
  113.                       ),
  114.                     ],
  115.                   ),
  116.                   child: TextField(
  117.                     decoration: InputDecoration(
  118.                       hintText: 'Search devices...',
  119.                       prefixIcon: const Icon(Icons.search_outlined, color: Color(0xFF6C757D), size: 18),
  120.                       hintStyle: const TextStyle(
  121.                         fontFamily: 'SansRegular',
  122.                         color: Color(0xFF6C757D),
  123.                         fontSize: 14,
  124.                       ),
  125.                       border: OutlineInputBorder(
  126.                         borderRadius: BorderRadius.circular(12),
  127.                         borderSide: BorderSide.none,
  128.                       ),
  129.                       contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  130.                       filled: true,
  131.                       fillColor: Colors.white,
  132.                     ),
  133.                     style: const TextStyle(
  134.                       fontFamily: 'SansRegular',
  135.                       fontSize: 14,
  136.                     ),
  137.                     onChanged: (value) => setState(() => _searchQuery = value.trim()),
  138.                   ),
  139.                 ),
  140.                 const SizedBox(height: 12),
  141.                 // First Row - Type, Floor, Building Filters
  142.                 Row(
  143.                   children: [
  144.                     // Type Filter
  145.                     Expanded(
  146.                       child: Container(
  147.                         height: 32,
  148.                         padding: const EdgeInsets.symmetric(horizontal: 10),
  149.                         decoration: BoxDecoration(
  150.                           color: Colors.white,
  151.                           borderRadius: BorderRadius.circular(8),
  152.                           boxShadow: [
  153.                             BoxShadow(
  154.                               color: Colors.black.withOpacity(0.05),
  155.                               blurRadius: 4,
  156.                               offset: const Offset(0, 1),
  157.                             ),
  158.                           ],
  159.                         ),
  160.                         child: Row(
  161.                           children: [
  162.                             const Icon(Icons.category_outlined, size: 14, color: Color(0xFF6C757D)),
  163.                             const SizedBox(width: 6),
  164.                             Expanded(
  165.                               child: DropdownButton<String>(
  166.                                 value: _selectedType,
  167.                                 dropdownColor: Colors.white,
  168.                                 underline: const SizedBox(),
  169.                                 isExpanded: true,
  170.                                 isDense: true,
  171.                                 style: const TextStyle(
  172.                                   color: Color(0xFF212529),
  173.                                   fontFamily: 'SansRegular',
  174.                                   fontSize: 12,
  175.                                 ),
  176.                                 items: const [
  177.                                   DropdownMenuItem(value: 'All', child: Text('All')),
  178.                                   DropdownMenuItem(value: 'PC', child: Text('PC')),
  179.                                   DropdownMenuItem(value: 'Peripheral', child: Text('Peripheral')),
  180.                                   DropdownMenuItem(value: 'Unknown', child: Text('Unknown')),
  181.                                 ],
  182.                                 onChanged: (value) => setState(() => _selectedType = value!),
  183.                               ),
  184.                             ),
  185.                           ],
  186.                         ),
  187.                       ),
  188.                     ),
  189.                     const SizedBox(width: 8),
  190.                     // Floor Filter
  191.                     Expanded(
  192.                       child: Container(
  193.                         height: 32,
  194.                         padding: const EdgeInsets.symmetric(horizontal: 10),
  195.                         decoration: BoxDecoration(
  196.                           color: Colors.white,
  197.                           borderRadius: BorderRadius.circular(8),
  198.                           boxShadow: [
  199.                             BoxShadow(
  200.                               color: Colors.black.withOpacity(0.05),
  201.                               blurRadius: 4,
  202.                               offset: const Offset(0, 1),
  203.                             ),
  204.                           ],
  205.                         ),
  206.                         child: Row(
  207.                           children: [
  208.                             const Icon(Icons.layers_outlined, size: 14, color: Color(0xFF6C757D)),
  209.                             const SizedBox(width: 6),
  210.                             Expanded(
  211.                               child: DropdownButton<String>(
  212.                                 value: _selectedFloor,
  213.                                 dropdownColor: Colors.white,
  214.                                 underline: const SizedBox(),
  215.                                 isExpanded: true,
  216.                                 isDense: true,
  217.                                 style: const TextStyle(
  218.                                   color: Color(0xFF212529),
  219.                                   fontFamily: 'SansRegular',
  220.                                   fontSize: 12,
  221.                                 ),
  222.                                 items: const [
  223.                                   DropdownMenuItem(value: 'All', child: Text('All')),
  224.                                   DropdownMenuItem(value: 'Ground Floor', child: Text('Ground')),
  225.                                   DropdownMenuItem(value: '1st Floor', child: Text('1st')),
  226.                                   DropdownMenuItem(value: '2nd Floor', child: Text('2nd')),
  227.                                   DropdownMenuItem(value: '3rd Floor', child: Text('3rd')),
  228.                                 ],
  229.                                 onChanged: (value) => setState(() => _selectedFloor = value!),
  230.                               ),
  231.                             ),
  232.                           ],
  233.                         ),
  234.                       ),
  235.                     ),
  236.                     const SizedBox(width: 8),
  237.                     // Building Filter - Now Dynamic
  238.                     Expanded(
  239.                       child: Container(
  240.                         height: 32,
  241.                         padding: const EdgeInsets.symmetric(horizontal: 10),
  242.                         decoration: BoxDecoration(
  243.                           color: Colors.white,
  244.                           borderRadius: BorderRadius.circular(8),
  245.                           boxShadow: [
  246.                             BoxShadow(
  247.                               color: Colors.black.withOpacity(0.05),
  248.                               blurRadius: 4,
  249.                               offset: const Offset(0, 1),
  250.                             ),
  251.                           ],
  252.                         ),
  253.                         child: Row(
  254.                           children: [
  255.                             const Icon(Icons.business_outlined, size: 14, color: Color(0xFF6C757D)),
  256.                             const SizedBox(width: 6),
  257.                             Expanded(
  258.                               child: _isBuildingDataLoaded
  259.                                 ? DropdownButton<String>(
  260.                                     value: _selectedBuilding,
  261.                                     dropdownColor: Colors.white,
  262.                                     underline: const SizedBox(),
  263.                                     isExpanded: true,
  264.                                     isDense: true,
  265.                                     style: const TextStyle(
  266.                                       color: Color(0xFF212529),
  267.                                       fontFamily: 'SansRegular',
  268.                                       fontSize: 12,
  269.                                     ),
  270.                                     items: _buildingOptions.map((building) {
  271.                                       return DropdownMenuItem(
  272.                                         value: building,
  273.                                         child: Text(
  274.                                           building == 'All' ? 'All' : building,
  275.                                           overflow: TextOverflow.ellipsis,
  276.                                         ),
  277.                                       );
  278.                                     }).toList(),
  279.                                     onChanged: (value) => setState(() => _selectedBuilding = value!),
  280.                                   )
  281.                                 : const SizedBox(
  282.                                     width: 12,
  283.                                     height: 12,
  284.                                     child: CircularProgressIndicator(
  285.                                       strokeWidth: 1,
  286.                                       valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF6C757D)),
  287.                                     ),
  288.                                   ),
  289.                             ),
  290.                           ],
  291.                         ),
  292.                       ),
  293.                     ),
  294.                   ],
  295.                 ),
  296.                 const SizedBox(height: 8),
  297.                 // Second Row - Status Filter (centered)
  298.                 Row(
  299.                   children: [
  300.                     Expanded(flex: 1, child: Container()), // Left spacer
  301.                     Expanded(
  302.                       flex: 2,
  303.                       child: Container(
  304.                         height: 32,
  305.                         padding: const EdgeInsets.symmetric(horizontal: 10),
  306.                         decoration: BoxDecoration(
  307.                           color: Colors.white,
  308.                           borderRadius: BorderRadius.circular(8),
  309.                           boxShadow: [
  310.                             BoxShadow(
  311.                               color: Colors.black.withOpacity(0.05),
  312.                               blurRadius: 4,
  313.                               offset: const Offset(0, 1),
  314.                             ),
  315.                           ],
  316.                         ),
  317.                         child: Row(
  318.                           children: [
  319.                             const Icon(Icons.wifi_outlined, size: 14, color: Color(0xFF6C757D)),
  320.                             const SizedBox(width: 6),
  321.                             Expanded(
  322.                               child: DropdownButton<String>(
  323.                                 value: _selectedStatus,
  324.                                 dropdownColor: Colors.white,
  325.                                 underline: const SizedBox(),
  326.                                 isExpanded: true,
  327.                                 isDense: true,
  328.                                 style: const TextStyle(
  329.                                   color: Color(0xFF212529),
  330.                                   fontFamily: 'SansRegular',
  331.                                   fontSize: 12,
  332.                                 ),
  333.                                 items: const [
  334.                                   DropdownMenuItem(value: 'All', child: Text('All Status')),
  335.                                   DropdownMenuItem(value: 'Online', child: Text('Online')),
  336.                                   DropdownMenuItem(value: 'Offline', child: Text('Offline')),
  337.                                 ],
  338.                                 onChanged: (value) => setState(() => _selectedStatus = value!),
  339.                               ),
  340.                             ),
  341.                           ],
  342.                         ),
  343.                       ),
  344.                     ),
  345.                     Expanded(flex: 1, child: Container()), // Right spacer
  346.                   ],
  347.                 ),
  348.               ],
  349.             ),
  350.           ),
  351.         ),
  352.       ),
  353.       body: FutureBuilder<Map<String, Map<String, String>>>(
  354.         future: _fetchLocationInfoMap(),
  355.         builder: (context, locationSnapshot) {
  356.           if (locationSnapshot.connectionState == ConnectionState.waiting) {
  357.             return const Center(
  358.               child: CircularProgressIndicator(
  359.                 valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFFFC727)),
  360.               ),
  361.             );
  362.           }
  363.           if (!locationSnapshot.hasData) {
  364.             return const Center(
  365.               child: Text(
  366.                 'Failed to load location data',
  367.                 style: TextStyle(
  368.                   fontFamily: 'SansRegular',
  369.                   color: Color(0xFF6C757D),
  370.                   fontSize: 14,
  371.                 ),
  372.               ),
  373.             );
  374.           }
  375.  
  376.           final locationInfoMap = locationSnapshot.data!;
  377.  
  378.           return StreamBuilder<QuerySnapshot>(
  379.             stream: FirebaseFirestore.instance.collection('devices').orderBy('name').snapshots(),
  380.             builder: (context, deviceSnapshot) {
  381.               if (deviceSnapshot.hasError) {
  382.                 return const Center(
  383.                   child: Text(
  384.                     'Error loading devices',
  385.                     style: TextStyle(
  386.                       fontFamily: 'SansRegular',
  387.                       color: Color(0xFF6C757D),
  388.                       fontSize: 14,
  389.                     ),
  390.                   ),
  391.                 );
  392.               }
  393.               if (deviceSnapshot.connectionState == ConnectionState.waiting) {
  394.                 return const Center(
  395.                   child: CircularProgressIndicator(
  396.                     valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFFFC727)),
  397.                   ),
  398.                 );
  399.               }
  400.  
  401.               final docs = deviceSnapshot.data!.docs;
  402.  
  403.               final filteredDevices = docs.map((doc) {
  404.                 final data = doc.data() as Map<String, dynamic>;
  405.                 final locationName = data['location'] ?? '';
  406.                 final locationInfo = locationInfoMap[locationName];
  407.                 data['floor'] = locationInfo?['floor'] ?? 'Unknown';
  408.                 data['building'] = locationInfo?['building'] ?? 'Unknown';
  409.                 data['locationName'] = locationName;
  410.                 data['id'] = doc.id;
  411.                 return data;
  412.               }).where((data) {
  413.                 final name = (data['name'] ?? '').toString().toLowerCase();
  414.                 final type = (data['type'] ?? 'Unknown').toString();
  415.                 final floor = (data['floor'] ?? 'Unknown').toString();
  416.                 final building = (data['building'] ?? 'Unknown').toString();
  417.                 final status = (data['status'] ?? '').toString();
  418.  
  419.                 if (_searchQuery.isNotEmpty && !name.contains(_searchQuery.toLowerCase())) return false;
  420.                 if (_selectedType != 'All' && type != _selectedType) return false;
  421.                 if (_selectedFloor != 'All' && floor != _selectedFloor) return false;
  422.                 if (_selectedBuilding != 'All' && building != _selectedBuilding) return false;
  423.                 if (_selectedStatus != 'All' && status.toLowerCase() != _selectedStatus.toLowerCase()) return false;
  424.                 return true;
  425.               }).toList();
  426.  
  427.               final grouped = <String, List<Map<String, dynamic>>>{};
  428.               for (var data in filteredDevices) {
  429.                 final name = (data['name'] ?? 'No name').toString();
  430.                 final key = name[0].toUpperCase();
  431.                 grouped.putIfAbsent(key, () => []).add(data);
  432.               }
  433.  
  434.               if (filteredDevices.isEmpty) {
  435.                 return Center(
  436.                   child: Column(
  437.                     mainAxisAlignment: MainAxisAlignment.center,
  438.                     children: [
  439.                       Icon(
  440.                         Icons.search_off_outlined,
  441.                         size: 48,
  442.                         color: Colors.grey[400],
  443.                       ),
  444.                       const SizedBox(height: 12),
  445.                       const Text(
  446.                         'No devices found',
  447.                         style: TextStyle(
  448.                           fontFamily: 'SansRegular',
  449.                           color: Color(0xFF6C757D),
  450.                           fontSize: 16,
  451.                           fontWeight: FontWeight.w500,
  452.                         ),
  453.                       ),
  454.                     ],
  455.                   ),
  456.                 );
  457.               }
  458.  
  459.               return ListView(
  460.                 padding: const EdgeInsets.all(16),
  461.                 children: grouped.entries.expand((entry) {
  462.                   return [
  463.                     Padding(
  464.                       padding: const EdgeInsets.only(left: 4, bottom: 8, top: 16),
  465.                       child: Text(
  466.                         entry.key,
  467.                         style: const TextStyle(
  468.                           fontSize: 16,
  469.                           fontWeight: FontWeight.w600,
  470.                           fontFamily: 'SansRegular',
  471.                           color: Color(0xFF212529),
  472.                           letterSpacing: 0.5,
  473.                         ),
  474.                       ),
  475.                     ),
  476.                     ...entry.value.map((data) => Container(
  477.                           margin: const EdgeInsets.only(bottom: 8),
  478.                           decoration: BoxDecoration(
  479.                             color: const Color(0xFF212529),
  480.                             borderRadius: BorderRadius.circular(12),
  481.                             boxShadow: [
  482.                               BoxShadow(
  483.                                 color: Colors.black.withOpacity(0.08),
  484.                                 blurRadius: 8,
  485.                                 offset: const Offset(0, 2),
  486.                               ),
  487.                             ],
  488.                           ),
  489.                           child: Material(
  490.                             color: Colors.transparent,
  491.                             child: InkWell(
  492.                               borderRadius: BorderRadius.circular(12),
  493.                               onTap: () {
  494.                                 Navigator.push(
  495.                                   context,
  496.                                   MaterialPageRoute(
  497.                                     builder: (context) => DeviceDetailsPage(device: data),
  498.                                   ),
  499.                                 );
  500.                               },
  501.                               child: Padding(
  502.                                 padding: const EdgeInsets.all(12),
  503.                                 child: Row(
  504.                                   children: [
  505.                                     Container(
  506.                                       padding: const EdgeInsets.all(8),
  507.                                       decoration: BoxDecoration(
  508.                                         color: const Color(0xFFFFC727).withOpacity(0.1),
  509.                                         borderRadius: BorderRadius.circular(8),
  510.                                       ),
  511.                                       child: _getDeviceIcon(data['type']),
  512.                                     ),
  513.                                     const SizedBox(width: 12),
  514.                                     Expanded(
  515.                                       child: Column(
  516.                                         crossAxisAlignment: CrossAxisAlignment.start,
  517.                                         children: [
  518.                                           Text(
  519.                                             data['name'] ?? 'No name',
  520.                                             style: const TextStyle(
  521.                                               fontFamily: 'SansRegular',
  522.                                               fontWeight: FontWeight.w600,
  523.                                               fontSize: 14,
  524.                                               color: Color(0xFFFFC727),
  525.                                             ),
  526.                                             maxLines: 1,
  527.                                             overflow: TextOverflow.ellipsis,
  528.                                           ),
  529.                                           const SizedBox(height: 4),
  530.                                           Row(
  531.                                             children: [
  532.                                               _buildInfoChip('${data['type'] ?? 'Unknown'}'),
  533.                                               const SizedBox(width: 6),
  534.                                               _buildInfoChip('${data['floor'] ?? 'Unknown'}'),
  535.                                             ],
  536.                                           ),
  537.                                           // Show peripheral_type chip below if it exists and is not empty
  538.                                           if (data['peripheral_type'] != null &&
  539.                                               data['peripheral_type'].toString().isNotEmpty) ...[
  540.                                             const SizedBox(height: 4),
  541.                                             Row(
  542.                                               children: [
  543.                                                 _buildInfoChip('${data['peripheral_type']}'),
  544.                                               ],
  545.                                             ),
  546.                                           ],
  547.                                           const SizedBox(height: 4),
  548.                                           Text(
  549.                                             '${data['locationName'] ?? 'Unknown Location'}',
  550.                                             style: const TextStyle(
  551.                                               fontFamily: 'SansRegular',
  552.                                               color: Color(0xFFADB5BD),
  553.                                               fontSize: 11,
  554.                                             ),
  555.                                             maxLines: 1,
  556.                                             overflow: TextOverflow.ellipsis,
  557.                                           ),
  558.                                           if (data['ip'] != null && data['ip'].toString().isNotEmpty) ...[
  559.                                             const SizedBox(height: 2),
  560.                                             Text(
  561.                                               'IP: ${data['ip']}',
  562.                                               style: const TextStyle(
  563.                                                 fontFamily: 'SansRegular',
  564.                                                 color: Color(0xFF6C757D),
  565.                                                 fontSize: 10,
  566.                                               ),
  567.                                             ),
  568.                                           ],
  569.                                         ],
  570.                                       ),
  571.                                     ),
  572.                                     Container(
  573.                                       padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
  574.                                       decoration: BoxDecoration(
  575.                                         color: _getStatusColor(data['status'] ?? '').withOpacity(0.2),
  576.                                         borderRadius: BorderRadius.circular(12),
  577.                                         border: Border.all(
  578.                                           color: _getStatusColor(data['status'] ?? ''),
  579.                                           width: 1,
  580.                                         ),
  581.                                       ),
  582.                                       child: Text(
  583.                                         (data['status'] ?? 'N/A').toString().toUpperCase(),
  584.                                         style: TextStyle(
  585.                                           color: _getStatusColor(data['status'] ?? ''),
  586.                                           fontWeight: FontWeight.w600,
  587.                                           fontFamily: 'SansRegular',
  588.                                           fontSize: 9,
  589.                                           letterSpacing: 0.3,
  590.                                         ),
  591.                                       ),
  592.                                     ),
  593.                                   ],
  594.                                 ),
  595.                               ),
  596.                             ),
  597.                           ),
  598.                         )),
  599.                   ];
  600.                 }).toList(),
  601.               );
  602.             },
  603.           );
  604.         },
  605.       ),
  606.     );
  607.   }
  608.  
  609.   Widget _buildInfoChip(String text) {
  610.     return Container(
  611.       padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
  612.       decoration: BoxDecoration(
  613.         color: const Color(0xFFFFC727).withOpacity(0.15),
  614.         borderRadius: BorderRadius.circular(6),
  615.       ),
  616.       child: Text(
  617.         text,
  618.         style: const TextStyle(
  619.           fontFamily: 'SansRegular',
  620.           color: Color(0xFFFFC727),
  621.           fontSize: 10,
  622.           fontWeight: FontWeight.w500,
  623.         ),
  624.       ),
  625.     );
  626.   }
  627. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement