import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/orders/delivery_updates.dart'; import 'package:supplier_new/orders/order_requests.dart'; import 'package:supplier_new/orders/orders_model.dart'; import 'package:supplier_new/orders/search_order_appbar.dart'; import 'package:intl/intl.dart'; import 'assign_driver.dart'; import 'cancel_order.dart'; import 'change_driver.dart'; class AllOrders extends StatefulWidget { final String navigationFrom; final TextEditingController? externalSearchController; AllOrders({ super.key, required this.navigationFrom, this.externalSearchController, }); @override State createState() => _AllOrdersState(); } class _AllOrdersState extends State { late TextEditingController searchController; bool isLoading=false; final List orders =[]; List allOrders = []; List ordersList = []; bool isSearching = false; int todaysOrdersCount = 0; int boreWaterCount = 0; int drinkingWaterCount = 0; String? selectedFilter; @override void initState() { super.initState(); _fetchOrders(); searchController = widget.externalSearchController ?? TextEditingController(); searchController.addListener(_searchOrders); } @override void dispose(){ searchController.removeListener(_searchOrders); if(widget.externalSearchController == null){ searchController.dispose(); } super.dispose(); } Future _fetchOrders() async { setState(() => isLoading = true); try { final response = await AppSettings.getAcceptedOrdersFromUsers(); final data = (jsonDecode(response)['data'] as List) .map((e) => OrdersModel.fromJson(e)) .toList(); if (!mounted) return; setState(() { allOrders = data; ordersList = List.from(data); ordersList.sort((a, b) { try { DateTime da = DateFormat("dd-MMM-yyyy").parse(a.date); DateTime db = DateFormat("dd-MMM-yyyy").parse(b.date); return db.compareTo(da); } catch (_) { return 0; } }); DateTime today = DateTime.now(); todaysOrdersCount = data.where((o) { try { DateTime d = DateFormat("dd-MMM-yyyy").parse(o.date); return d.year == today.year && d.month == today.month && d.day == today.day; } catch (_) { return false; } }).length; boreWaterCount = data .where((o) => o.type_of_water.toLowerCase().contains("bore")) .length; drinkingWaterCount = data .where((o) => o.type_of_water.toLowerCase().contains("drinking")) .length; isLoading = false; }); } catch (e) { debugPrint("⚠️ Error fetching orders: $e"); setState(() => isLoading = false); } } void _searchOrders() { String query = searchController.text.trim().toLowerCase(); if(query.isEmpty){ setState(() { ordersList = List.from(allOrders); isSearching = false; }); return; } List filtered = []; for(var order in allOrders){ String building = order.building_name.toString().toLowerCase(); String address = order.displayAddress.toString().toLowerCase(); String water = order.type_of_water.toString().toLowerCase(); String status = order.status.toString().toLowerCase(); String capacity = order.capacity.toString().toLowerCase(); String amount = order.quoted_amount.toString().toLowerCase(); String date = order.date.toString().toLowerCase(); String time = order.time.toString().toLowerCase(); String driver = order.delivery_agent_name.toString().toLowerCase(); String tanker = order.tanker_name.toString().toLowerCase(); /// normalize status if(status=='advance_paid' || status=='accepted'){ status='pending'; } if(status=='deliveryboy_assigned' || status=='tanker_assigned'){ status='assigned'; } if(status=='delivered'){ status='completed'; } if( building.contains(query) || address.contains(query) || water.contains(query) || status.contains(query) || capacity.contains(query) || amount.contains(query) || date.contains(query) || time.contains(query) || driver.contains(query) || tanker.contains(query) ){ filtered.add(order); } } if(!mounted) return; setState(() { ordersList = filtered; isSearching = true; }); } void sortOrders(String type) { setState(() { selectedFilter = type; if (type == "Building") { ordersList.sort((a, b) => a.building_name.toLowerCase().compareTo(b.building_name.toLowerCase())); } if (type == "Status") { ordersList.sort((a, b) => a.status.toLowerCase().compareTo(b.status.toLowerCase())); } if (type == "Date") { ordersList.sort((a, b) { try { DateTime da = DateFormat("dd-MMM-yyyy").parse(a.date); DateTime db = DateFormat("dd-MMM-yyyy").parse(b.date); // newest first, oldest last return db.compareTo(da); } catch (_) { return 0; } }); } if (type == "Amount") { ordersList.sort((a, b) => double.parse(b.quoted_amount.toString()) .compareTo(double.parse(a.quoted_amount.toString()))); } }); } @override Widget build(BuildContext context) { // Group orders by date Map> groupedOrders = {}; String formatOrderDate(String? dateStr) { if (dateStr == null || dateStr.trim().isEmpty) { return ""; } try { final inputFormat = DateFormat("dd-MMM-yyyy"); final outputFormat = DateFormat("dd MMM yyyy"); final parsedDate = inputFormat.parse(dateStr); return outputFormat.format(parsedDate); } catch (e) { print("Date parse error: $e"); return dateStr; } } for (var order in ordersList) { final formattedDate = formatOrderDate(order.date); groupedOrders.putIfAbsent(formattedDate, () => []).add(order); } return Scaffold( backgroundColor: Color(0XFFF2F2F2), appBar: widget.navigationFrom.toString().toLowerCase()=='dashboard'? SearchOrderAppBar( controller: searchController, onBack: () => Navigator.pop(context), onHelp: () {}, onSearch: (value){ _searchOrders(); }, ):null, body: isLoading ? const Center(child: CircularProgressIndicator()) : ordersList.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children:[ Icon(Icons.search_off,size:60,color:Colors.grey), SizedBox(height:10), Text( isSearching? "No matching orders found": "No Data Available", style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700), ), ], ), ):SingleChildScrollView( child: Padding( padding: EdgeInsets.all(16), child: Column( children: [ SizedBox(height: MediaQuery.of(context).size.height * .042), /// Total Orders Column( children: [ Text( "$todaysOrdersCount", style:fontTextStyle(64, Color(0XFFFF2D2E30), FontWeight.w700), ), Text( "Today's Total Orders", style: fontTextStyle(24, Color(0XFFFF2D2E30), FontWeight.w600), ), SizedBox(height: MediaQuery.of(context).size.height * .042), /// Bore Water + Drinking Water Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ OrderCategoryCard( image: Image.asset( 'images/bore-water.png', fit: BoxFit.contain, height: 40, width: 40, ), value: "$boreWaterCount", label: "Bore Water", ), OrderCategoryCard( image: Image.asset( 'images/drinking-water.png', height: 40, width: 40, fit: BoxFit.contain, ), value: "$drinkingWaterCount", label: "Drinking Water", ), ], ), SizedBox(height: MediaQuery.of(context).size.height * .024), /// Button Container( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor:Color(0XFF8270DB), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(32), ), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16), ), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => OrderRequestsPage()), ); }, child: Text( "View Order Requests", style: fontTextStyle(16, Color(0XFFFFFFFFFF), FontWeight.w500), ), ), ) ], ), const SizedBox(height: 20), /// Filters Row SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ FilterChipWidget(label: "Building", onTap: () => sortOrders("Building")), const SizedBox(width: 8), FilterChipWidget(label: "Status", onTap: () => sortOrders("Status")), const SizedBox(width: 8), FilterChipWidget(label: "Date", onTap: () => sortOrders("Date")), const SizedBox(width: 8), FilterChipWidget(label: "Amount", onTap: () => sortOrders("Amount")), ], ), ), const SizedBox(height: 20), /// Order List ListView( padding: const EdgeInsets.all(12), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), children: groupedOrders.entries.map((entry) { final date = entry.key; final ordersForDate = entry.value; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(0), child: Text( date, style:fontTextStyle(14, Color(0XFF343637), FontWeight.w600), ), ), ...ordersForDate.map((order) => OrderCard( order: order, onRefresh: _fetchOrders, )), const SizedBox(height: 12), ], ); }).toList(), ), ], ), ) ), ); } } /// Category Card class OrderCategoryCard extends StatelessWidget { final Image image; final String value; final String label; const OrderCategoryCard({ super.key, required this.image, required this.value, required this.label, }); @override Widget build(BuildContext context) { return Column( children: [ SizedBox( width: 40, height: 40, child: image, ), SizedBox(height: MediaQuery.of(context).size.height * .008), Text( value, style: fontTextStyle(20, Color(0XFFFF515253), FontWeight.w700), ), SizedBox(height: MediaQuery.of(context).size.height * .008), Text(label, style: fontTextStyle(16, Color(0XFFFF515253), FontWeight.w400),), ], ); } } /// Filter Chip class FilterChipWidget extends StatelessWidget { final String label; final VoidCallback? onTap; const FilterChipWidget({ super.key, required this.label, this.onTap, }); @override Widget build(BuildContext context) { return ChoiceChip( label: Text(label), selected: false, onSelected: (_) { if (onTap != null) onTap!(); }, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), backgroundColor: Colors.white, side: BorderSide(color: Colors.grey.shade300), ); } } class OrderCard extends StatefulWidget { final OrdersModel order; final VoidCallback? onRefresh; const OrderCard({ super.key, required this.order, this.onRefresh }); @override State createState() => _OrderCardState(); } class _OrderCardState extends State{ Timer? timer; @override void initState(){ super.initState(); timer = Timer.periodic( const Duration(minutes:1), (_) { if(mounted){ setState(() {}); } } ); } @override void dispose(){ timer?.cancel(); super.dispose(); } /// DATE PARSE DateTime? get orderDate{ try{ return DateFormat("dd-MMM-yyyy") .parse(widget.order.date); }catch(_){ return null; } } /// TODAY DATE ONLY DateTime get today{ final now = DateTime.now(); return DateTime( now.year, now.month, now.day ); } /// ORDER DAY ONLY DateTime? get orderDay{ if(orderDate==null){ return null; } return DateTime( orderDate!.year, orderDate!.month, orderDate!.day ); } /// EXPIRE ONLY IF BEFORE TODAY bool get isExpired{ if(orderDay==null){ return false; } String s = widget.order.status.toLowerCase(); /// Only pending orders expire if( s!='advance_paid' && s!='accepted' ){ return false; } return orderDay!.isBefore(today); } /// STATUS NORMALIZATION String get normalizedStatus{ if(isExpired){ return "expired"; } String s = widget.order.status.toLowerCase(); if(s=='advance_paid' || s=='accepted'){ return "pending"; } if(s=='deliveryboy_assigned' || s=='tanker_assigned'){ return "assigned"; } if(s=='delivered'){ return "completed"; } /// delivery flow statuses if( s=='in_progress' || s=='pickup_started' || s=='start_loading' || s=='loading_completed' || s=='out_for_delivery' || s=='arrived' || s=='unloading_started' || s=='unloading_stopped' || s=='payment_pending' ){ return s.replaceAll('_',' '); } return s; } /// ACTION RULES bool get canAssign{ if(orderDay == null){ return false; } if(orderDay!.isBefore(today)){ return false; } String s = widget.order.status.toLowerCase(); /// only before delivery starts return s=='advance_paid' || s=='accepted'; } bool get canCancel{ if(orderDay == null){ return false; } if(orderDay!.isBefore(today)){ return false; } String s = widget.order.status.toLowerCase(); return s=='advance_paid' || s=='accepted' || s=='in_progress' || s=='pickup_started' || s=='start_loading' || s=='loading_completed' || s=='out_for_delivery' || s=='arrived' || s=='payment_pending'; } bool get canTrack{ if(orderDay == null){ return false; } if(orderDay!.isBefore(today)){ return false; } String s = widget.order.status.toLowerCase(); return s=='in_progress' || s=='pickup_started' || s=='start_loading' || s=='loading_completed' || s=='out_for_delivery' || s=='arrived' || s=='unloading_started' || s=='unloading_stopped' || s=='payment_pending'; } bool get canChange{ if(isExpired){ return false; } return normalizedStatus=='assigned'; } bool get canChangeAction{ if(orderDay == null){ return false; } /// disable for past orders if(orderDay!.isBefore(today)){ return false; } return normalizedStatus == 'assigned'; } bool get isCompleted{ return normalizedStatus=='completed'; } /// PRIORITY = FUTURE TODAY ORDERS ONLY bool get isPriority{ if(orderDay==null){ return false; } return orderDay==today && normalizedStatus=='pending'; } /// STATUS COLOR Color get statusColor{ switch(normalizedStatus){ case "expired": return Color(0XFFFCEDEC); case "completed": return Color(0XFFC4E8C3); case "in progress": case "pickup started": case "start loading": case "loading completed": case "out for delivery": case "arrived": case "unloading started": case "unloading stopped": case "payment pending": return Color(0XFFC9DFFE); case "cancelled": return Color(0XFFFCEDEC); case "assigned": return Color(0XFFF9DBC6); case "pending": return Color(0XFFFDF3D3); default: return Colors.grey.shade200; } } /// TEXT COLOR Color get textStatusColor{ switch(normalizedStatus){ case "expired": return Color(0XFFE2483D); case "completed": return Color(0XFF0A9E04); case "in progress": case "pickup started": case "start loading": case "loading completed": case "out for delivery": case "arrived": case "unloading started": case "unloading stopped": case "payment pending": return Color(0XFF1D7AFC); case "cancelled": return Color(0XFFE2483D); case "assigned": return Color(0XFFE56910); case "pending": return Color(0XFFD0AE3C); default: return Colors.black87; } } /// PRIORITY BADGE Widget buildPriority(){ if(!isPriority){ return SizedBox(); } return Container( padding: EdgeInsets.symmetric( horizontal:6, vertical:2 ), decoration: BoxDecoration( color: Color(0XFFFFE0B2), borderRadius: BorderRadius.circular(4) ), child: Text( "TODAY", style: fontTextStyle( 8, Color(0XFFE56910), FontWeight.w700 ), ), ); } @override Widget build(BuildContext context){ return Padding( padding:EdgeInsets.fromLTRB(0,8,0,0), child:Container( margin:EdgeInsets.only(bottom:12), decoration:BoxDecoration( color:Colors.white, borderRadius:BorderRadius.circular(12), boxShadow:[ BoxShadow( color:Colors.black.withOpacity(0.05), blurRadius:5, offset:Offset(0,3) ) ] ), child:IntrinsicHeight( child:Row( crossAxisAlignment:CrossAxisAlignment.stretch, children:[ ClipRRect( borderRadius:BorderRadius.only( topLeft:Radius.circular(12), bottomLeft:Radius.circular(12) ), child:SizedBox( width:145, child:widget.order.imageAsset.isNotEmpty ? Image.asset(widget.order.imageAsset,fit:BoxFit.cover) : Image.network(widget.order.imageAsset,fit:BoxFit.cover) ), ), Expanded( child:Padding( padding:EdgeInsets.all(10), child:Column( crossAxisAlignment:CrossAxisAlignment.start, children:[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children:[ Container( padding:EdgeInsets.symmetric( horizontal:8, vertical:2 ), decoration:BoxDecoration( color:statusColor, borderRadius:BorderRadius.circular(4) ), child:Text( normalizedStatus, style:fontTextStyle( 10, textStatusColor, FontWeight.w400 ), ), ), buildPriority() ], ), SizedBox(height:6), if(normalizedStatus == "cancelled" && widget.order.remarks.isNotEmpty) Padding( padding: EdgeInsets.only(top:4), child: Container( padding: EdgeInsets.all(6), decoration: BoxDecoration( color: Color(0XFFFCEDEC), borderRadius: BorderRadius.circular(6), border: Border.all( color: Color(0XFFE2483D) ), ), child: Row( children:[ Icon( Icons.info_outline, size:14, color: Color(0XFFE2483D) ), SizedBox(width:6), Expanded( child: Text( "${widget.order.remarks}", style: fontTextStyle( 10, Color(0XFFE2483D), FontWeight.w400 ), ), ) ], ), ), ), Text( widget.order.building_name, style:fontTextStyle( 16, Color(0XFF2D2E30), FontWeight.w600 ) ), Text( widget.order.displayAddress, style:fontTextStyle( 12, Color(0XFF646566), FontWeight.w400 ) ), SizedBox(height:4), Text( '${widget.order.capacity} - ${widget.order.type_of_water}', style:fontTextStyle( 14, Color(0XFF444444), FontWeight.w500 ) ), Text( '${widget.order.time} , ${widget.order.date}', style:fontTextStyle( 8, Color(0XFF646566), FontWeight.w400 ) ), SizedBox(height:12), /// ASSIGN CANCEL Visibility( visible:canAssign, child:Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children:[ GestureDetector( onTap:canAssign?() async{ final result= await Navigator.push( context, MaterialPageRoute( builder:(context)=> AssignDriverScreen( order:widget.order ) ) ); if(result==true){ widget.onRefresh?.call(); } }:null, child:Container( decoration:BoxDecoration( borderRadius: BorderRadius.circular(22), border:Border.all( color:canAssign ? Color(0XFF939495) : Colors.grey ) ), child:Padding( padding: EdgeInsets.fromLTRB(8,4,8,4), child:Text( "Assign", style:TextStyle( fontSize:14, color:canAssign ? Color(0XFF515253) : Colors.grey, fontWeight:FontWeight.w400 ), ), ), ), ), GestureDetector( onTap:canCancel?() async{ final result= await Navigator.push( context, MaterialPageRoute( builder:(context)=> CancelOrderScreen( order:widget.order, status:widget.order.status ) ) ); if(result==true){ widget.onRefresh?.call(); } }:null, child:Container( decoration:BoxDecoration( borderRadius: BorderRadius.circular(22), border:Border.all( color:canCancel ? Color(0XFFE2483D) : Colors.grey ), color:canCancel ? Color(0XFFE2483D) : Colors.grey.shade400 ), child:Padding( padding: EdgeInsets.fromLTRB(8,4,8,4), child:Text( "Cancel", style:TextStyle( fontSize:14, color:Colors.white, fontWeight:FontWeight.w400 ), ), ), ), ), ], ), ), /// TRACK Visibility( visible:canTrack, child:GestureDetector( onTap:() async{ final result= await Navigator.push( context, MaterialPageRoute( builder:(context)=> DeliveryUpdatesPage( orderId:widget.order.dbId, initialStatus:widget.order.status ) ) ); if(result==true){ widget.onRefresh?.call(); } }, child:Container( decoration:BoxDecoration( borderRadius: BorderRadius.circular(22), color:Color(0XFF8270DB), border:Border.all( color:Color(0XFF8270DB) ) ), child:Padding( padding: EdgeInsets.fromLTRB(8,4,8,4), child:Text( "Track Delivery", style:TextStyle( fontSize:14, color:Colors.white, fontWeight:FontWeight.w400 ), ), ), ), ), ), /// ASSIGNED Visibility( visible:canChange, child:Column( crossAxisAlignment: CrossAxisAlignment.start, children:[ Row( children:[ Image.asset( 'images/avatar.png', width:12, height:12 ), SizedBox(width:8), Expanded( child:Text( widget.order.delivery_agent_name!='' ? "Assigned to ${widget.order.delivery_agent_name}" : "Assigned to ${widget.order.tanker_name}", maxLines:1, overflow:TextOverflow.ellipsis, style:fontTextStyle( 8, Color(0XFF646566), FontWeight.w400 ), ), ) ], ), SizedBox(height:6), GestureDetector( onTap: canChangeAction ? () async{ final result = await Navigator.push( context, MaterialPageRoute( builder:(context)=> ChangeDriverScreen( order:widget.order ) ) ); if(result==true){ widget.onRefresh?.call(); } } : null, child:Container( decoration:BoxDecoration( borderRadius: BorderRadius.circular(22), color: canChangeAction ? Color(0XFF8270DB) : Colors.grey.shade400, border:Border.all( color: canChangeAction ? Color(0XFF8270DB) : Colors.grey ) ), child:Padding( padding: EdgeInsets.fromLTRB(8,4,8,4), child:Text( "Change", style:TextStyle( fontSize:14, color: canChangeAction ? Colors.white : Colors.white70, fontWeight:FontWeight.w400 ), ), ), ), ), ], ), ), /// COMPLETED Visibility( visible:isCompleted, child:Row( children:[ Image.asset( 'images/avatar.png', width:12, height:12 ), SizedBox(width:8), Expanded( child:Text( widget.order.delivery_agent_name!='' ? "Delivered by ${widget.order.delivery_agent_name}" : "Assigned to ${widget.order.tanker_name}", maxLines:1, overflow:TextOverflow.ellipsis, style:fontTextStyle( 8, Color(0XFF646566), FontWeight.w400 ), ), ) ], ), ), ], ), ), ) ], ), ), ), ); } }