parent
888acb1cb5
commit
aa8f316106
@ -1,530 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:supplier_new/common/settings.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
import '../common/settings.dart';
|
||||
|
||||
class SupplyCalendarScreen extends StatefulWidget {
|
||||
@override
|
||||
State<SupplyCalendarScreen> createState() => _SupplyCalendarScreenState();
|
||||
}
|
||||
|
||||
class _SupplyCalendarScreenState extends State<SupplyCalendarScreen> {
|
||||
late DateTime _focusedDay;
|
||||
late DateTime _firstDay;
|
||||
late DateTime _lastDay;
|
||||
|
||||
Map<DateTime, List<Map<String, dynamic>>> calendarEvents = {};
|
||||
DateTime? _selectedDay;
|
||||
|
||||
bool isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final now = DateTime.now();
|
||||
_focusedDay = now;
|
||||
|
||||
_firstDay = DateTime(2025, 1, 1);
|
||||
_lastDay = DateTime(now.year, now.month + 6, 0);
|
||||
|
||||
fetchOrdersFromApi();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// FETCH API → BUILD TWO EVENTS (ORIGINAL + RESCHEDULED)
|
||||
// ===========================================================================
|
||||
Future<void> fetchOrdersFromApi() async {
|
||||
try {
|
||||
final response = await AppSettings.getAcceptedOrdersFromUsers();
|
||||
final decoded = jsonDecode(response);
|
||||
|
||||
if (decoded == null || decoded['data'] == null) {
|
||||
setState(() => isLoading = false);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<dynamic> orders = decoded['data'];
|
||||
calendarEvents.clear();
|
||||
|
||||
for (var order in orders) {
|
||||
String? originalDate = order["date"];
|
||||
String? newDate = order["reScheduleDateOfDelivery"];
|
||||
String? resStatus = order["rescheduleOrderStatus"];
|
||||
|
||||
bool isRescheduled =
|
||||
newDate != null && newDate.isNotEmpty && resStatus != null && resStatus.isNotEmpty;
|
||||
|
||||
// ===================== ORIGINAL DATE EVENT =====================
|
||||
if (originalDate != null && order["orderStatus"] != "cancelled") {
|
||||
try {
|
||||
DateTime d = DateTime.parse(originalDate);
|
||||
DateTime key = DateTime(d.year, d.month, d.day);
|
||||
|
||||
calendarEvents.putIfAbsent(key, () => []);
|
||||
calendarEvents[key]!.add({
|
||||
"status": isRescheduled ? "rescheduled_from" : "delivery",
|
||||
"_id": order["_id"],
|
||||
"supplierName": order["supplierName"] ?? "Supplier",
|
||||
"capacity": order["capacity"] ?? "",
|
||||
"time": order["time"] ?? "",
|
||||
"originalDate": originalDate,
|
||||
"newDate": newDate,
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ===================== NEW RESCHEDULED DATE EVENT =====================
|
||||
if (isRescheduled) {
|
||||
try {
|
||||
DateTime d2 = DateTime.parse(newDate);
|
||||
DateTime key2 = DateTime(d2.year, d2.month, d2.day);
|
||||
|
||||
calendarEvents.putIfAbsent(key2, () => []);
|
||||
calendarEvents[key2]!.add({
|
||||
"status": "rescheduled_to",
|
||||
"_id": order["_id"],
|
||||
"supplierName": order["supplierName"] ?? "Supplier",
|
||||
"capacity": order["capacity"] ?? "",
|
||||
"time": order["time"] ?? "",
|
||||
"originalDate": originalDate,
|
||||
"newDate": newDate,
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ===================== CANCELLED EVENT =====================
|
||||
if (order["orderStatus"] == "cancelled") {
|
||||
try {
|
||||
DateTime d = DateTime.parse(originalDate!);
|
||||
DateTime key = DateTime(d.year, d.month, d.day);
|
||||
|
||||
calendarEvents.putIfAbsent(key, () => []);
|
||||
calendarEvents[key]!.add({
|
||||
"status": "cancelled",
|
||||
"_id": order["_id"],
|
||||
"supplierName": order["supplierName"] ?? "Supplier",
|
||||
"capacity": order["capacity"] ?? "",
|
||||
"time": order["time"] ?? "",
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
setState(() => isLoading = false);
|
||||
} catch (e) {
|
||||
setState(() => isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// CANCEL ORDER API
|
||||
// ===========================================================================
|
||||
Future<void> 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<String> 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<String, dynamic> 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<void> openRescheduleCalendar(Map<String, dynamic> 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 sendRescheduleToServer(delivery["_id"], formatted);
|
||||
}
|
||||
|
||||
Future<void> 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<String, dynamic> 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);
|
||||
_confirmCancel(delivery["_id"]);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
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,
|
||||
daysOfWeekHeight: 40,
|
||||
|
||||
onPageChanged: (focused) {
|
||||
setState(() => _focusedDay = focused);
|
||||
},
|
||||
|
||||
onDaySelected: (selected, focused) {
|
||||
_focusedDay = focused;
|
||||
_selectedDay = selected;
|
||||
showDeliveryList(selected);
|
||||
setState(() {});
|
||||
},
|
||||
|
||||
calendarBuilders: CalendarBuilders(
|
||||
defaultBuilder: (context, date, _) {
|
||||
final key = DateTime(date.year, date.month, date.day);
|
||||
final events = calendarEvents[key];
|
||||
|
||||
Color bg = Colors.transparent;
|
||||
|
||||
if (events != null && events.isNotEmpty) {
|
||||
String s = events.first["status"];
|
||||
if (s == "rescheduled_to")
|
||||
bg = Colors.deepOrange.withOpacity(0.10);
|
||||
else if (s == "rescheduled_from")
|
||||
bg = Colors.orange.withOpacity(0.10);
|
||||
else if (s == "cancelled")
|
||||
bg = Colors.red.withOpacity(0.10);
|
||||
else
|
||||
bg = Colors.blue.withOpacity(0.08);
|
||||
}
|
||||
|
||||
// Grouping counts (delivery, cancelled, rescheduled)
|
||||
Map<String, int> grouped = {};
|
||||
if (events != null) {
|
||||
for (var e in events) {
|
||||
grouped[e["status"]] = (grouped[e["status"]] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
border: Border.all(color: Colors.grey.shade300)),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("${date.day}",
|
||||
style: TextStyle(
|
||||
fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
|
||||
if (events != null && events.isNotEmpty)
|
||||
Wrap(
|
||||
spacing: 3,
|
||||
alignment: WrapAlignment.center,
|
||||
children: grouped.entries.map((entry) {
|
||||
IconData icon;
|
||||
Color color;
|
||||
|
||||
if (entry.key == "rescheduled_from") {
|
||||
icon = Icons.subdirectory_arrow_left;
|
||||
color = Colors.orange;
|
||||
}
|
||||
else if (entry.key == "rescheduled_to") {
|
||||
icon = Icons.subdirectory_arrow_right;
|
||||
color = Colors.deepOrange;
|
||||
}
|
||||
else if (entry.key == "cancelled") {
|
||||
icon = Icons.cancel;
|
||||
color = Colors.red;
|
||||
}
|
||||
else {
|
||||
icon = Icons.local_shipping;
|
||||
color = Colors.blue;
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 10, color: color),
|
||||
Text("x${entry.value}",
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color)),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue