import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:table_calendar/table_calendar.dart'; import '../common/settings.dart'; class SupplierCalendar extends StatefulWidget { @override State createState() => _SupplierCalendarState(); } class _SupplierCalendarState extends State { late DateTime _focusedDay; late DateTime _firstDay; late DateTime _lastDay; Map>> calendarEvents = {}; DateTime? _selectedDay; bool isLoading = true; Future cancelSingleDate(Map delivery) async { try { await AppSettings.recurringDateAction( delivery["_id"], { "action": "cancel", "date": delivery["date"], // IMPORTANT "reason": "Cancelled by supplier", }, ); await fetchOrdersFromApi(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Delivery cancelled successfully")), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Cancel failed")), ); } } Future rescheduleSingleDate( Map delivery, String newDate, ) async { try { await AppSettings.recurringDateAction( delivery["_id"], { "action": "reschedule", "date": delivery["date"], // original date "new_date": newDate, // selected date "reason": "Rescheduled by supplier", }, ); await fetchOrdersFromApi(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Delivery rescheduled")), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Reschedule failed")), ); } } @override void initState() { super.initState(); final now = DateTime.now(); _focusedDay = now.add(const Duration(days: 2)); _firstDay = DateTime(2025, 1, 1); _lastDay = DateTime(now.year, now.month + 6, 0); fetchOrdersFromApi(); } // =========================================================================== // FETCH API → BUILD TWO EVENTS (ORIGINAL + RESCHEDULED) // =========================================================================== Future fetchOrdersFromApi() async { try { final response = await AppSettings.getSupplierBookings(); final decoded = jsonDecode(response); if (decoded == null || decoded['data'] == null) { setState(() => isLoading = false); return; } final List orders = decoded['data']; calendarEvents.clear(); for (var order in orders) { final String capacity = order["capacity"] ?? ""; final String time = order["time"] ?? ""; final String bookingStatus = order["booking_status"] ?? ""; final String supplierName = order["customer_details"]?["profile"]?["username"] ?? "Customer"; final List dates = order["dates"] ?? []; for (var d in dates) { final String? dateStr = d["date"]; if (dateStr == null) continue; DateTime date = DateTime.parse(dateStr); DateTime key = DateTime(date.year, date.month, date.day); calendarEvents.putIfAbsent(key, () => []); // ---------------- STATUS LOGIC ---------------- String status = "delivery"; if (d["status"] == "rescheduled") { status = "rescheduled_from"; } if (d["rescheduled_from"] != null) { status = "rescheduled_to"; } if (d["status"] == "cancelled") { status = "cancelled"; } calendarEvents[key]!.add({ "status": status, "_id": order["_id"], "supplierName": supplierName, "capacity": capacity, "time": time, "date": dateStr, "originalDate": d["rescheduled_from"], "newDate": d["rescheduled_to"], }); } } setState(() => isLoading = false); } catch (e) { setState(() => isLoading = false); } } // =========================================================================== // CANCEL ORDER API // =========================================================================== /*Future cancelDelivery(String id) async { try { await AppSettings.cancelPlanOrder(id); // your endpoint await fetchOrdersFromApi(); ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text("Order cancelled successfully"))); } catch (e) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text("Cancel failed"))); } }*/ /*void _confirmCancel(String orderId) { showDialog( context: context, builder: (_) => AlertDialog( title: Text("Cancel Delivery"), content: Text("Are you sure you want to cancel this delivery?"), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text("No")), TextButton( onPressed: () { Navigator.pop(context); cancelDelivery(orderId); }, child: Text("Yes, Cancel", style: TextStyle(color: Colors.red))), ], ), ); }*/ // =========================================================================== // HELPER FUNCTIONS // =========================================================================== String formatShort(String date) { try { final d = DateTime.parse(date); return "${d.day} ${monthShort[d.month - 1]}"; } catch (e) { return date; } } List monthShort = [ "Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec" ]; Widget _buildStatusIcon(String status) { if (status == "rescheduled_from") { return Icon(Icons.subdirectory_arrow_left, color: Colors.orange); } if (status == "rescheduled_to") { return Icon(Icons.subdirectory_arrow_right, color: Colors.deepOrange); } if (status == "cancelled") { return Icon(Icons.cancel, color: Colors.red); } return Icon(Icons.local_shipping, color: Colors.blue); } String _buildStatusText(Map d) { String status = d["status"]; String? originalDate = d["originalDate"]; String? newDate = d["newDate"]; if (status == "rescheduled_from") { return "Rescheduled from this date → ${formatShort(newDate!)}"; } if (status == "rescheduled_to") { return "Rescheduled to this date (from ${formatShort(originalDate!)})"; } if (status == "cancelled") { return "Cancelled"; } return status; } // =========================================================================== // OPEN RESCHEDULE CALENDAR // =========================================================================== Future openRescheduleCalendar(Map delivery) async { DateTime now = DateTime.now(); final DateTime? pickedDate = await showDatePicker( context: context, initialDate: now.add(Duration(days: 1)), firstDate: now.add(Duration(days: 1)), lastDate: DateTime(now.year + 1, 12, 31), ); if (pickedDate == null) return; String formatted = "${pickedDate.year}-${pickedDate.month.toString().padLeft(2,'0')}-${pickedDate.day.toString().padLeft(2,'0')}"; await rescheduleSingleDate(delivery, formatted); //await sendRescheduleToServer(delivery["_id"], formatted); } /*Future sendRescheduleToServer(String id, String newDate) async { try { final payload = {"reScheduleDateOfDelivery": newDate}; await AppSettings.rescheduleOrder(id, payload); await fetchOrdersFromApi(); ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text("Delivery rescheduled to $newDate"))); } catch (e) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text("Reschedule failed"))); } } */ // =========================================================================== // SHOW DELIVERY LIST // =========================================================================== void showDeliveryList(DateTime date) { final key = DateTime(date.year, date.month, date.day); final events = calendarEvents[key]; if (events == null || events.isEmpty) return; bool isPast = date.isBefore(DateTime( DateTime.now().year, DateTime.now().month, DateTime.now().day)); showModalBottomSheet( context: context, isScrollControlled: true, builder: (_) { final list = events.toList(); return DraggableScrollableSheet( initialChildSize: 0.9, maxChildSize: 0.9, minChildSize: 0.4, builder: (_, controller) { return Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( children: [ Row( children: [ Expanded( child: Text( "Select Delivery", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), GestureDetector( onTap: () => Navigator.pop(context), child: Icon(Icons.close)), ], ), Expanded( child: ListView( controller: controller, children: list.map((delivery) { return ListTile( leading: _buildStatusIcon(delivery["status"]), title: Text(_buildStatusText(delivery)), subtitle: Text( "Capacity: ${delivery["capacity"]} • ${delivery["time"]}"), onTap: () { Navigator.pop(context); showActionsForSingleDelivery(delivery, isPast); }, ); }).toList(), ), ) ], ), ); }, ); }, ); } // =========================================================================== // ACTIONS SHEET // =========================================================================== void showActionsForSingleDelivery(Map delivery, bool isPast) { String status = delivery["status"]; // ❌ ORIGINAL DATE → NO ACTIONS if (status == "rescheduled_from") { showModalBottomSheet( context: context, builder: (_) { return Container( padding: EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text("Rescheduled from this date", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), SizedBox(height: 10), Text("This delivery was moved to another date.", style: TextStyle(color: Colors.grey)), ], ), ); }, ); return; } // ❌ CANCELLED ORDER → NO ACTIONS if (status == "cancelled") { showModalBottomSheet( context: context, builder: (_) => Container( padding: EdgeInsets.all(20), child: Text("Order was cancelled", style: TextStyle(color: Colors.red)), ), ); return; } // NORMAL OR RESCHEDULED_TO (ACTIONS ENABLED) showModalBottomSheet( context: context, builder: (_) { return Container( padding: EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text("${delivery["supplierName"]}", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), SizedBox(height: 16), if (!isPast) ...[ ListTile( leading: Icon(Icons.calendar_month, color: Colors.blue), title: Text("Reschedule Delivery"), onTap: () { Navigator.pop(context); openRescheduleCalendar(delivery); }, ), ListTile( leading: Icon(Icons.delete_forever, color: Colors.red), title: Text("Cancel Delivery"), onTap: () { Navigator.pop(context); cancelSingleDate(delivery); }, ), ], if (isPast) Text("Past delivery — actions disabled", style: TextStyle(color: Colors.grey)), ], ), ); }, ); } // =========================================================================== // BUILD UI // =========================================================================== @override Widget build(BuildContext context) { if (isLoading) { return Scaffold(body: Center(child: CircularProgressIndicator())); } final height = MediaQuery.of(context).size.height; return Scaffold( backgroundColor: Colors.white, body: Column( children: [ SizedBox(height: 50), Text( "${monthShort[_focusedDay.month - 1]} ${_focusedDay.year}", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), Expanded( child: TableCalendar( firstDay: _firstDay, lastDay: _lastDay, focusedDay: _focusedDay, headerVisible: false, calendarFormat: CalendarFormat.month, rowHeight: (height - 200) / 6 + 12, daysOfWeekHeight: 40, calendarStyle: CalendarStyle( selectedDecoration: const BoxDecoration( color: Colors.transparent, // remove selected circle ), todayDecoration: const BoxDecoration( color: Colors.transparent, // 🚫 removes TODAY highlight ), todayTextStyle: const TextStyle( color: Colors.red, // same as normal day fontWeight: FontWeight.bold, ), tableBorder: TableBorder( horizontalInside: BorderSide( color: Colors.grey.shade300, width: 1, ), ), ), onPageChanged: (focused) { setState(() => _focusedDay = focused); }, onDaySelected: (selected, focused) { _focusedDay = focused; _selectedDay = selected; showDeliveryList(selected); setState(() {}); }, calendarBuilders: CalendarBuilders( /// 🔴 TODAY BUILDER (dot only here) todayBuilder: (context, date, _) { final key = DateTime(date.year, date.month, date.day); final events = calendarEvents[key]; Map grouped = {}; if (events != null) { for (var e in events) { grouped[e["status"]] = (grouped[e["status"]] ?? 0) + 1; } } return Container( margin: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.only(top: 1), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // 🔴 Today date Text( "${date.day}", style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.red, ), ), // 🔴 Dot ONLY for today const SizedBox(height: 2), Container( width: 5, height: 5, decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), ), // Events OR reserved space if (events != null && events.isNotEmpty) Wrap( spacing: 3, alignment: WrapAlignment.center, children: grouped.entries.map((entry) { String label; IconData icon; Color color; if (entry.key == "rescheduled_from" || entry.key == "rescheduled_to") { label = "Rescheduled"; icon = Icons.update; color = Colors.orange; } else if (entry.key == "cancelled") { label = "Cancelled"; icon = Icons.cancel; color = Colors.red; } else { label = "Delivery"; icon = Icons.local_shipping; color = Colors.blue; } return SizedBox( height: 26, child: Container( padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 2), decoration: BoxDecoration( color: color.withOpacity(0.12), borderRadius: BorderRadius.zero, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 10, color: color), const SizedBox(height: 1), FittedBox( fit: BoxFit.scaleDown, child: Text( "$label x${entry.value}", style: TextStyle( fontSize: 7, fontWeight: FontWeight.bold, color: color, ), ), ), ], ), ), ); }).toList(), ) else const SizedBox(height: 26), // keep height same ], ), ); }, /// 📅 NORMAL DAYS (NO DOT) defaultBuilder: (context, date, _) { final key = DateTime(date.year, date.month, date.day); final events = calendarEvents[key]; Map grouped = {}; if (events != null) { for (var e in events) { grouped[e["status"]] = (grouped[e["status"]] ?? 0) + 1; } } return Container( margin: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.only(top: 1), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "${date.day}", style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), ), if (events != null && events.isNotEmpty) Wrap( spacing: 3, alignment: WrapAlignment.center, children: grouped.entries.map((entry) { String label; IconData icon; Color color; if (entry.key == "rescheduled_from" || entry.key == "rescheduled_to") { label = "Rescheduled"; icon = Icons.update; color = Colors.orange; } else if (entry.key == "cancelled") { label = "Cancelled"; icon = Icons.cancel; color = Colors.red; } else { label = "Delivery"; icon = Icons.local_shipping; color = Colors.blue; } return SizedBox( height: 26, child: Container( padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 2), decoration: BoxDecoration( color: color.withOpacity(0.12), borderRadius: BorderRadius.zero, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 10, color: color), const SizedBox(height: 1), FittedBox( fit: BoxFit.scaleDown, child: Text( "$label x${entry.value}", style: TextStyle( fontSize: 7, fontWeight: FontWeight.bold, color: color, ), ), ), ], ), ), ); }).toList(), ) else const SizedBox(height: 26), ], ), ); }, /// 🌫 Outside month days outsideBuilder: (context, date, _) { return Container( margin: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.only(top: 1), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "${date.day}", style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey.shade400, ), ), ] ), ); }, ), ), ), ], ), ); } }