From a7a868fcdd36dcb48e6b8481f239e41849de5938 Mon Sep 17 00:00:00 2001 From: Sneha Date: Fri, 23 Jan 2026 17:02:16 +0530 Subject: [PATCH] changes --- images/right_button.png | Bin 0 -> 4496 bytes images/wrong_button.png | Bin 0 -> 4621 bytes lib/common/calander.dart | 646 ++++++++++++----- lib/common/dashboard.dart | 146 +++- lib/common/settings.dart | 255 ++++++- .../building_transactions_details.dart | 302 ++++---- lib/financials/create_credit_accounts.dart | 222 ++++-- lib/financials/financial_main_screen.dart | 556 +++++++------- lib/plans/accept_plan_requests.dart | 490 +++++++++++-- lib/plans/all_plans.dart | 668 +++++++++-------- lib/plans/calendar.dart | 682 ++++++++++++++++++ lib/plans/plan_details.dart | 311 ++++---- lib/plans/plan_model_new.dart | 96 +++ lib/plans/plan_requests_model.dart | 6 + lib/plans/plans_model.dart | 20 + lib/resources/resources_drivers.dart | 52 +- lib/resources/resources_fleet.dart | 35 +- lib/resources/resources_main.dart | 7 +- lib/resources/resources_sources.dart | 69 +- pubspec.lock | 16 + pubspec.yaml | 1 + 21 files changed, 3384 insertions(+), 1196 deletions(-) create mode 100644 images/right_button.png create mode 100644 images/wrong_button.png create mode 100644 lib/plans/calendar.dart create mode 100644 lib/plans/plan_model_new.dart diff --git a/images/right_button.png b/images/right_button.png new file mode 100644 index 0000000000000000000000000000000000000000..522a7b8057e9bc72dfa6ffc479382850387ffff3 GIT binary patch literal 4496 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 createState() => _DeliveryCalendarScreenState(); + State createState() => _SupplyCalendarScreenState(); } -class _DeliveryCalendarScreenState extends State { - DateTime _focusedMonth = DateTime(2025, 10); - late List> _calendarData; +class _SupplyCalendarScreenState extends State { + late DateTime _focusedDay; + late DateTime _firstDay; + late DateTime _lastDay; + + Map>> calendarEvents = {}; + DateTime? _selectedDay; + + bool isLoading = true; @override void initState() { super.initState(); - _calendarData = _generateCalendarData(); + + 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 fetchOrdersFromApi() async { + try { + final response = await AppSettings.getAcceptedOrdersFromUsers(); + 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) { + 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 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"))); + }*/ } - List> _generateCalendarData() { - return [ - {"day": 1, "status": "Delivered"}, - {"day": 2, "status": "Delivered"}, - {"day": 3, "status": "Rescheduled"}, - {"day": 4, "status": "Delivered"}, - {"day": 5, "status": "Delivered"}, - {"day": 6, "status": "Delivered"}, - {"day": 7, "status": "Cancelled"}, - {"day": 8, "status": "Delivered"}, - {"day": 9, "status": "Delivered"}, - {"day": 10, "status": "Delivered"}, - {"day": 11, "status": "Cancelled"}, - {"day": 12, "status": "Delivery"}, - {"day": 13, "status": "Delivery"}, - {"day": 14, "status": "Delivery"}, - {"day": 15, "status": "Delivery"}, - {"day": 16, "status": "Delivery"}, - {"day": 17, "status": "Delivery"}, - {"day": 18, "status": "Delivery"}, - {"day": 19, "status": "Delivery"}, - {"day": 20, "status": "Delivery"}, - {"day": 21, "status": "Delivery"}, - {"day": 22, "status": "Delivery"}, - {"day": 23, "status": "Delivery"}, - {"day": 24, "status": "Delivery"}, - {"day": 25, "status": "Delivery"}, - {"day": 26, "status": "Delivery"}, - ]; + 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))), + ], + ), + ); } - Color _getBackgroundColor(String status) { - switch (status) { - case "Delivered": - return const Color(0xFFE6F4EA); - case "Cancelled": - return const Color(0xFFFDE8E8); - case "Rescheduled": - return const Color(0xFFF2F2F2); - case "Delivery": - return const Color(0xFFEFF4FF); - default: - return Colors.white; + // =========================================================================== + // HELPER FUNCTIONS + // =========================================================================== + String formatShort(String date) { + try { + final d = DateTime.parse(date); + return "${d.day} ${monthShort[d.month - 1]}"; + } catch (e) { + return date; } } - Color _getTextColor(String status) { - switch (status) { - case "Delivered": - return Colors.green; - case "Cancelled": - return Colors.red; - case "Rescheduled": - return Colors.black54; - case "Delivery": - return const Color(0xFF3B6FE0); - default: - return Colors.black87; + 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); } - Widget _getStatusIcon(String status) { - switch (status) { - case "Delivered": - return const Icon(Icons.check, size: 16, color: Colors.green); - case "Cancelled": - return const Icon(Icons.close, size: 16, color: Colors.red); - case "Rescheduled": - return const Icon(Icons.access_time, size: 16, color: Colors.black54); - case "Delivery": - return const Icon(Icons.local_shipping, size: 16, color: Color(0xFF3B6FE0)); - default: - return const SizedBox.shrink(); + 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; } - String _getMonthYear() { - return DateFormat('MMM yyyy').format(_focusedMonth).toUpperCase(); + // =========================================================================== + // 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 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(), + ), + ) + ], + ), + ); + }, + ); + }, + ); } - int _daysInMonth(DateTime date) { - final firstDayThisMonth = DateTime(date.year, date.month, 1); - final firstDayNextMonth = DateTime(date.year, date.month + 1, 1); - return firstDayNextMonth.difference(firstDayThisMonth).inDays; + // =========================================================================== + // 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); + _confirmCancel(delivery["_id"]); + }, + ), + ], + + if (isPast) + Text("Past delivery — actions disabled", + style: TextStyle(color: Colors.grey)), + ], + ), + ); + }, + ); } + // =========================================================================== + // BUILD UI + // =========================================================================== @override Widget build(BuildContext context) { - final int totalDays = _daysInMonth(_focusedMonth); - final int firstWeekday = DateTime(_focusedMonth.year, _focusedMonth.month, 1).weekday; - final int totalSlots = totalDays + (firstWeekday - 1); + if (isLoading) { + return Scaffold(body: Center(child: CircularProgressIndicator())); + } + + final height = MediaQuery.of(context).size.height; return Scaffold( backgroundColor: Colors.white, - appBar: AppBar( - backgroundColor: Colors.white, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black), - onPressed: () => Navigator.pop(context), - ), - title: Text("Calendar", style: fontTextStyle(16, Colors.black, FontWeight.w600)), - actions: const [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 8), - child: Icon(Icons.calendar_month_outlined, color: Color(0xFF8270DB)), - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 8), - child: Icon(Icons.notifications_none_rounded, color: Colors.black87), - ), - ], - ), body: Column( children: [ - const SizedBox(height: 8), - Text(_getMonthYear(), style: fontTextStyle(16, Colors.black, FontWeight.w600)), - const SizedBox(height: 8), - - // Weekdays Row - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Row( - children: const [ - Expanded(child: Center(child: Text("MON"))), - Expanded(child: Center(child: Text("TUE"))), - Expanded(child: Center(child: Text("WED"))), - Expanded(child: Center(child: Text("THU"))), - Expanded(child: Center(child: Text("FRI"))), - Expanded(child: Center(child: Text("SAT"))), - Expanded(child: Center(child: Text("SUN"))), - ], - ), + SizedBox(height: 50), + + Text( + "${monthShort[_focusedDay.month - 1]} ${_focusedDay.year}", + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), - const SizedBox(height: 8), - // Calendar Grid Expanded( - child: GridView.builder( - padding: const EdgeInsets.symmetric(horizontal: 4), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 7, - crossAxisSpacing: 4, - mainAxisSpacing: 4, - ), - itemCount: totalSlots, - itemBuilder: (context, index) { - if (index < firstWeekday - 1) { - return const SizedBox.shrink(); - } - final day = index - (firstWeekday - 2); - final status = _calendarData - .firstWhere( - (item) => item['day'] == day, - orElse: () => {"status": ""}, - )['status'] - .toString(); - - return Container( - decoration: BoxDecoration( - color: _getBackgroundColor(status), - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.grey.shade300), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "$day", - style: fontTextStyle(13, _getTextColor(status), FontWeight.w600), - ), - const SizedBox(height: 4), - _getStatusIcon(status), - ], - ), - ); + 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 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(), + ) + ], + ), + ), + ); + }, + ), ), ), ], diff --git a/lib/common/dashboard.dart b/lib/common/dashboard.dart index dfa166a..bd0b934 100644 --- a/lib/common/dashboard.dart +++ b/lib/common/dashboard.dart @@ -11,14 +11,17 @@ import 'package:supplier_new/orders/all_orders.dart'; import 'package:supplier_new/orders/order_requests.dart'; import 'package:supplier_new/orders/search_order_appbar.dart'; import 'package:supplier_new/plans/all_plans.dart'; +import 'package:supplier_new/plans/calendar.dart'; import 'package:supplier_new/plans/plan_requests.dart'; import 'package:supplier_new/plans/search_plan_appbar.dart'; import 'package:supplier_new/set_rates/set_rates.dart'; import '../login/login.dart'; import '../profile/fleet.dart'; +import '../resources/drivers_model.dart'; import '../resources/resources_fleet.dart'; import '../resources/resources_main.dart'; +import '../resources/tankers_model.dart'; import 'calander.dart'; class DashboardScreen extends StatefulWidget { @@ -323,7 +326,7 @@ class _DashboardScreenState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => DeliveryCalendarScreen(), + builder: (context) => SupplierCalendar(), ), ); }, @@ -763,6 +766,82 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { + @override + void initState() { + super.initState(); + _fetchDriverCounts(); + _fetchTankerCounts(); + } + + + Future _fetchDriverCounts() async { + try { + final response = await AppSettings.getDrivers(); + final data = (jsonDecode(response)['data'] as List) + .map((e) => DriversModel.fromJson(e)) + .toList(); + + int available = 0; + int onDelivery = 0; + int offline = 0; + + for (final d in data) { + switch (d.status) { + case 'available': + available++; + break; + case 'on delivery': + onDelivery++; + break; + case 'offline': + offline++; + break; + } + } + + if (!mounted) return; + setState(() { + AppSettings.driverAvailableCount = available; + AppSettings.driverOnDeliveryCount = onDelivery; + AppSettings.driverOfflineCount = offline; + AppSettings.totalDrivers = data.length; + }); + } catch (e) { + debugPrint("Driver dashboard error: $e"); + } + } + + // ================= TANKERS ================= + Future _fetchTankerCounts() async { + try { + final response = await AppSettings.getTankers(); + final data = (jsonDecode(response)['data'] as List) + .map((e) => TankersModel.fromJson(e)) + .toList(); + + int active = 0; + int maintenance = 0; + + for (final t in data) { + final statusList = List.from(t.availability); + if (statusList.contains('undermaintanence')) { + maintenance++; + } else { + active++; + } + } + + if (!mounted) return; + setState(() { + AppSettings.tankerActiveCount = active; + AppSettings.tankerMaintenanceCount = maintenance; + AppSettings.totalTankers = data.length; + }); + } catch (e) { + debugPrint("Tanker dashboard error: $e"); + } + } + @override Widget build(BuildContext context) { return SingleChildScrollView( @@ -851,18 +930,61 @@ class _HomeScreenState extends State { crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 1.5, - children: const [ - DashboardCard( - title: "Active Vehicles", - value: "12/14", - subtitle: "2 in maintenance", - icon: Icons.local_shipping_outlined, + children: [ + GestureDetector( + onTap: () async { + // 👉 Switch to Resources tab (index = 3) + final dashboardState = + context.findAncestorStateOfType<_DashboardScreenState>(); + + dashboardState?.setState(() { + dashboardState._currentIndex = 3; + }); + + // 👉 Wait for user to come back to Home + await Future.delayed(const Duration(milliseconds: 300)); + + // 👉 Refresh counts when back + _fetchTankerCounts(); + _fetchDriverCounts(); + }, + child: DashboardCard( + title: "Active Vehicles", + value: + "${AppSettings.tankerActiveCount}/${AppSettings.totalTankers}", + subtitle: + "${AppSettings.tankerMaintenanceCount} in maintenance", + icon: Icons.local_shipping_outlined, + ), ), - DashboardCard( - title: "Available Drivers", - value: "2/10", - subtitle: "6 on delivery, 2 offline", - icon: Icons.person_outline, + GestureDetector( + onTap: () async { + // 👉 Tell Resources to open Drivers tab + AppSettings.resourcesInitialTab = 1; // DRIVERS + + // 👉 Switch to Resources screen + final dashboardState = + context.findAncestorStateOfType<_DashboardScreenState>(); + + dashboardState?.setState(() { + dashboardState._currentIndex = 3; + }); + + // 👉 When user comes back to dashboard + await Future.delayed(const Duration(milliseconds: 300)); + + _fetchDriverCounts(); + _fetchTankerCounts(); + }, + child: DashboardCard( + title: "Available Drivers", + value: + "${AppSettings.driverAvailableCount}/${AppSettings.totalDrivers}", + subtitle: + "${AppSettings.driverOnDeliveryCount} on delivery, " + "${AppSettings.driverOfflineCount} offline", + icon: Icons.person_outline, + ), ), ], ), diff --git a/lib/common/settings.dart b/lib/common/settings.dart index 7c8305e..6bd9c19 100644 --- a/lib/common/settings.dart +++ b/lib/common/settings.dart @@ -158,6 +158,7 @@ class AppSettings{ static String getAcceptedOrdersFromUsersUrl = host + 'getAllTankersBookingdetails'; static String getPlanRequestsFromUsersUrl = host + 'getuserRequestbookingsforplansforsupplier'; static String acceptPlanRequestsUrl = host + 'supplier/recurring/respond'; + static String acceptPlanRequestsUrl1 = host + 'recurringRequestedBooking'; static String getTankersUrl = host + 'getTankers'; static String getTankerDetailsByNameUrl = host + 'getsingledetails'; static String addTankerUrl = host + 'addTankers'; @@ -179,10 +180,30 @@ class AppSettings{ static String updatePumpFeeUrl = host + 'suppliers'; static String assignTankerUrl = host + 'assign-deliveryboy-tanker'; static String getDriverDetailsByPhoneUrl = host + 'getsingledeliveryboydetails'; + static String getAcceptedRecurringBookingsUrl = host + 'supplier/recurring'; + static String getAllTransactionsUrl = host + 'supplierAccounts'; + static String getAdvanceTransactionsBySupplierUrl = host + 'advance/transactions/supplier'; + static String createAdvanceRequestUrl = host + 'advance/request'; + static String acceptAdvanceFromCustomerUrl = host + 'advance/confirm'; + static String getAdvanceTransactionsBySupplierAndCustomerUrl = host + 'advance/transactions'; + static String respondRecurringBookingUrl = host + 'customer/recurring/respond'; + static String getSupplierBookingsUrl = host + 'getsupplierbookings'; + static int driverAvailableCount = 0; + static int driverOnDeliveryCount = 0; + static int driverOfflineCount = 0; + static int totalDrivers = 0; + + static int tankerActiveCount = 0; + static int tankerMaintenanceCount = 0; + static int totalTankers = 0; + + static int resourcesInitialTab = 0; + static List existingCreditCustomerIds = []; + static String formDouble(dynamic s) { var comma = NumberFormat('#,##,###.##', 'en_IN'); @@ -504,15 +525,15 @@ class AppSettings{ static Future acceptPlanRequests(payload,dbId) async { - var uri = Uri.parse(acceptPlanRequestsUrl+'/'+dbId+'/'+AppSettings.supplierId); + var uri = Uri.parse(acceptPlanRequestsUrl1+'/'+dbId+'/action'); - var response = await http.put(uri, body: json.encode(payload), headers: await buildRequestHeaders()); + var response = await http.post(uri, body: json.encode(payload), headers: await buildRequestHeaders()); if (response.statusCode == 200) { return true; } else if (response.statusCode == 401) { bool status = await AppSettings.resetToken(); if (status) { - response = await http.put(uri,body: json.encode(payload), headers: await buildRequestHeaders()); + response = await http.post(uri,body: json.encode(payload), headers: await buildRequestHeaders()); if (response.statusCode == 200) { return true; } else { @@ -1175,6 +1196,234 @@ class AppSettings{ } } + static Future getAcceptedRecurringBookings() async { + var uri = Uri.parse("$getAcceptedRecurringBookingsUrl/${AppSettings.supplierId}/accepted"); + //uri = uri.replace(query: 'supplierId=$supplierId'); + + var response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else if (response.statusCode == 401) { + bool status = await AppSettings.resetToken(); + if (status) { + response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else { + return ''; + } + } else { + return ''; + } + } else { + return ''; + } + } + + static Future getSupplierTransactions() async { + var uri = Uri.parse("$getAllTransactionsUrl/${AppSettings.supplierId}"); + //uri = uri.replace(query: 'supplierId=$supplierId'); + + var response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else if (response.statusCode == 401) { + bool status = await AppSettings.resetToken(); + if (status) { + response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else { + return ''; + } + } else { + return ''; + } + } else { + return ''; + } + } + + static Future getAdvanceTransactionsBySupplier() async { + var uri = Uri.parse("$getAdvanceTransactionsBySupplierUrl/${AppSettings.supplierId}"); + //uri = uri.replace(query: 'supplierId=$supplierId'); + + var response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else if (response.statusCode == 401) { + bool status = await AppSettings.resetToken(); + if (status) { + response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else { + return ''; + } + } else { + return ''; + } + } else { + return ''; + } + } + + static Future createAdvanceRequest(Map payload,) async { + var uri = Uri.parse("$createAdvanceRequestUrl"); + + var response = await http.post( + uri, + body: json.encode(payload), + headers: await buildRequestHeaders(), + ); + + if (response.statusCode == 200) { + return true; + } + + // 🔁 Token expired → retry + if (response.statusCode == 401) { + final status = await AppSettings.resetToken(); + + if (status) { + response = await http.post( + uri, + body: json.encode(payload), + headers: await buildRequestHeaders(), + ); + + return response.statusCode == 200; + } + } + + return false; + } + + static Future acceptAdvanceFromCustomer(Map payload,) async { + var uri = Uri.parse("$acceptAdvanceFromCustomerUrl"); + + var response = await http.post( + uri, + body: json.encode(payload), + headers: await buildRequestHeaders(), + ); + + if (response.statusCode == 200) { + return true; + } + + // 🔁 Token expired → retry + if (response.statusCode == 401) { + final status = await AppSettings.resetToken(); + + if (status) { + response = await http.post( + uri, + body: json.encode(payload), + headers: await buildRequestHeaders(), + ); + + return response.statusCode == 200; + } + } + + return false; + } + + static Future confirmAdvanceReceived(String transactionId, String action,) async { + try { + var uri = Uri.parse("$acceptAdvanceFromCustomerUrl/$transactionId"); + + var payload = json.encode({ + "action": action, // "accept" + }); + + var response = await http.put(uri, body: payload, headers: await buildRequestHeaders(),); + + if (response.statusCode == 200) { + return true; + } + + // 🔁 Token expired → retry + if (response.statusCode == 401) { + bool refreshed = await resetToken(); + if (!refreshed) return false; + + response = await http.put( + uri, + body: payload, + headers: await buildRequestHeaders(), + ); + + return response.statusCode == 200; + } + + return false; + } catch (e) { + debugPrint("❌ confirmAdvanceReceived error: $e"); + return false; + } + } + + static Future getAdvanceTransactionsBySupplierAndCustomer( + String supplierId, + String customerId, + ) async { + var uri = Uri.parse("$getAdvanceTransactionsBySupplierAndCustomerUrl/$supplierId/$customerId"); + + + final response = await http.get( + uri, + headers: await buildRequestHeaders(), + ); + + if (response.statusCode == 200) { + return response.body; + } else { + throw Exception("Failed to fetch transactions"); + } + } + + static Future> respondRecurringBooking({ + required String bookingId, + required String supplierId, + required String action, // "accept" | "reject" + }) async { + + var uri = Uri.parse("$respondRecurringBookingUrl/$bookingId"); + + final response = await http.post( + uri, + headers: await buildRequestHeaders(), + body: jsonEncode({ + "supplierId": supplierId, + "action": action, + }), + ); + + if (response.statusCode == 200) { + return jsonDecode(response.body); + } else { + throw Exception("Failed to respond booking"); + } + } + + static Future getSupplierBookings() async { + var uri = Uri.parse("$getSupplierBookingsUrl/${AppSettings.supplierId}"); + + + final response = await http.get( + uri, + headers: await buildRequestHeaders(), + ); + + if (response.statusCode == 200) { + return response.body; + } else { + throw Exception("Failed to fetch transactions"); + } + } + /*Apis ends here*/ diff --git a/lib/financials/building_transactions_details.dart b/lib/financials/building_transactions_details.dart index 3f82ec1..4e3849d 100644 --- a/lib/financials/building_transactions_details.dart +++ b/lib/financials/building_transactions_details.dart @@ -1,92 +1,122 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/financials/add_transaction_for_credit_account.dart'; class BuildingTransactionsDetails extends StatefulWidget { - const BuildingTransactionsDetails({super.key}); + final String supplierId; + final String customerId; + + const BuildingTransactionsDetails({ + super.key, + required this.supplierId, + required this.customerId, + }); @override - State createState() => _BuildingTransactionsDetailsState(); + State createState() => + _BuildingTransactionsDetailsState(); } -class _BuildingTransactionsDetailsState extends State { +class _BuildingTransactionsDetailsState + extends State { + bool isLoading = true; + + List> transactions = []; + + double advanceBalance = 0; + double receivableBalance = 0; + + @override + void initState() { + super.initState(); + fetchTransactions(); + } + + // ========================= API ========================= + Future fetchTransactions() async { + try { + final response = + await AppSettings.getAdvanceTransactionsBySupplierAndCustomer( + widget.supplierId, + widget.customerId, + ); + + final decoded = jsonDecode(response); + final List list = decoded["data"] ?? []; + + double advance = 0; + double receivable = 0; + + final txns = list.map>((e) { + final amount = + double.tryParse(e["advance_amount"].toString()) ?? 0; + + if (amount >= 0) { + advance += amount; + } else { + receivable += amount.abs(); + } + + return { + "title": e["payment_type"] ?? "Transaction", + "date": e["date_of_transaction"] ?? "", + "amount": amount, + "isCredit": amount >= 0, + }; + }).toList(); + + setState(() { + transactions = txns; + advanceBalance = advance; + receivableBalance = receivable; + isLoading = false; + }); + } catch (e) { + debugPrint("❌ Transaction fetch error: $e"); + setState(() => isLoading = false); + } + } + + // ========================= UI ========================= @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: Colors.white, + appBar: AppSettings.supplierAppBarWithActionsText(widget.customerId, context), floatingActionButton: SizedBox( - width: 52, // default is 56 - height: 52, // make it bigger + width: 52, + height: 52, child: FloatingActionButton( - shape: const CircleBorder(), // ensures perfect round shape + shape: const CircleBorder(), backgroundColor: Colors.black, - onPressed: (){ + onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (_) => AddCreditTransactionPage(), ), ); - }, - child:Image.asset( - "images/plus.png", // your custom image + child: Image.asset( + "images/plus.png", width: 20, height: 20, - color: Colors.white, // optional: apply tint + color: Colors.white, ), ), ), body: SafeArea( child: Column( children: [ - // Header - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Green Valley Apartments", - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 4), - Text( - "Gachibowli", - style: TextStyle( - fontSize: 14, - color: Colors.grey.shade600, - ), - ), - ], - ), - TextButton( - onPressed: () {}, - child: const Text( - "HELP", - style: TextStyle(color: Colors.blue), - ), - ) - ], - ), - ), - // Statement button + // ================= STATEMENT ================= Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ OutlinedButton.icon( - style: OutlinedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), onPressed: () {}, icon: const Icon(Icons.download), label: const Text("Statement"), @@ -95,7 +125,7 @@ class _BuildingTransactionsDetailsState extends State _CreateCreditAccountScreenState(); + _CreateCreditAccountScreenState createState() => + _CreateCreditAccountScreenState(); } -class _CreateCreditAccountScreenState extends State { - String? selectedCustomer; - String paymentTerm = 'Net 30'; - final creditLimitController = TextEditingController(text: '₹500'); - final openingBalanceController = TextEditingController(text: '₹500'); - final List> customers = [ - {'name': 'Ramakrishna', 'date': '20 August'}, - {'name': 'Mallesham Water Supplies', 'date': '21 August'}, - {'name': 'My Home Bhooja', 'date': '21 August'}, - ]; - final List paymentTerms = ['Net 15', 'Net 30', 'Net 45']; + +class _CreateCreditAccountScreenState + extends State { + String? selectedCustomerId; + + final creditLimitController = TextEditingController(); + final openingBalanceController = TextEditingController(); + + bool isLoading = true; + + List> customers = []; + + @override + void initState() { + super.initState(); + _fetchCustomers(); + } + + Future _fetchCustomers() async { + try { + final response = await AppSettings.getAcceptedOrdersFromUsers(); + final decoded = jsonDecode(response); + + final List data = decoded["data"] ?? []; + + /// Already having credit accounts + final existingCustomerIds = AppSettings.existingCreditCustomerIds; + + /// 🔑 Track unique customers + final Set seenCustomerIds = {}; + final List> filtered = []; + + for (final e in data) { + final customerId = e["customerId"]?.toString(); + + if (customerId == null) continue; + + // ❌ Skip if already has credit account + if (existingCustomerIds.contains(customerId)) continue; + + // ❌ Skip duplicates + if (seenCustomerIds.contains(customerId)) continue; + + seenCustomerIds.add(customerId); + + filtered.add({ + "customerId": customerId, + "name": e["customerName"] ?? "Unknown", + "date": e["createdAt"]?.toString().substring(0, 10) ?? "", + }); + } + + setState(() { + customers = filtered; + isLoading = false; + }); + } catch (e) { + debugPrint("⚠️ Create account customer fetch error: $e"); + setState(() => isLoading = false); + } + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Create Credit Account'), - actions: [ - TextButton( - onPressed: () {}, - child: Text('HELP', style: TextStyle(color: Colors.blue)), - ) - ], + title: const Text('Create Credit Account'), backgroundColor: Colors.white, foregroundColor: Colors.black, elevation: 1, ), - body: Padding( + body: isLoading + ? const Center(child: CircularProgressIndicator()) + : Padding( padding: const EdgeInsets.all(16), child: ListView( children: [ - Text('SELECT CUSTOMER', style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 10), + /// SELECT CUSTOMER + const Text( + 'SELECT CUSTOMER', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + + if (customers.isEmpty) + const Center( + child: Text( + "No eligible customers available", + style: TextStyle(color: Colors.grey), + ), + ), + ...customers.map((customer) { + final isSelected = + selectedCustomerId == customer["customerId"]; + return GestureDetector( onTap: () { setState(() { - selectedCustomer = customer['name']; + selectedCustomerId = customer["customerId"]; }); }, child: Container( @@ -49,21 +114,30 @@ class _CreateCreditAccountScreenState extends State { padding: const EdgeInsets.all(12), decoration: BoxDecoration( border: Border.all( - color: selectedCustomer == customer['name'] ? Colors.black : Colors.grey.shade300, + color: + isSelected ? Colors.black : Colors.grey.shade300, width: 1.5, ), borderRadius: BorderRadius.circular(8), - color: Colors.white, ), child: Row( children: [ - CircleAvatar(child: Icon(Icons.person)), + const CircleAvatar( + child: Icon(Icons.person), + ), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(customer['name']!, style: TextStyle(fontSize: 16)), - Text(customer['date']!, style: TextStyle(color: Colors.grey)), + Text( + customer["name"], + style: const TextStyle(fontSize: 16), + ), + Text( + customer["date"], + style: + const TextStyle(color: Colors.grey), + ), ], ) ], @@ -71,73 +145,91 @@ class _CreateCreditAccountScreenState extends State { ), ); }), + const SizedBox(height: 20), - Text('ENTER DETAILS', style: TextStyle(fontWeight: FontWeight.bold)), + + /// ENTER DETAILS + const Text( + 'ENTER DETAILS', + style: TextStyle(fontWeight: FontWeight.bold), + ), const SizedBox(height: 10), + TextField( controller: creditLimitController, keyboardType: TextInputType.number, - decoration: InputDecoration( - labelText: 'Credit Limit (in ₹) *', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 15), - DropdownButtonFormField2( - decoration: InputDecoration( - labelText: 'Payment Terms *', + decoration: const InputDecoration( + labelText: 'Credit Limit (₹) *', border: OutlineInputBorder(), ), - value: paymentTerm, - items: paymentTerms.map((term) { - return DropdownMenuItem( - value: term, - child: Text(term), - ); - }).toList(), - onChanged: (value) { - setState(() { - paymentTerm = value!; - }); - }, ), + const SizedBox(height: 15), + TextField( controller: openingBalanceController, keyboardType: TextInputType.number, - decoration: InputDecoration( - labelText: 'Opening Balance (in ₹) *', + decoration: const InputDecoration( + labelText: 'Opening Balance (₹) *', border: OutlineInputBorder(), ), ), + const SizedBox(height: 10), + Row( - children: [ - Icon(Icons.info_outline, color: Colors.orange, size: 18), - const SizedBox(width: 8), + children: const [ + Icon(Icons.info_outline, + color: Colors.orange, size: 18), + SizedBox(width: 8), Expanded( child: Text( - 'Creating an Account will send the customer a notification about the details and a request to add balance to start the water delivery plan.', - style: TextStyle(color: Colors.black87, fontSize: 13), + 'Creating an account will notify the customer and request balance to start water delivery.', + style: TextStyle(fontSize: 13), ), ) ], ), + const SizedBox(height: 20), + ElevatedButton( - onPressed: () { - // Add your submission logic here + onPressed: selectedCustomerId == null + ? null + : () async{ + final payload = { + "supplierId": AppSettings.supplierId, + "customerId": selectedCustomerId, + "advance_amount": double.tryParse(openingBalanceController.text) ?? 0, + "exceed_limit": double.tryParse(creditLimitController.text) ?? 0, + }; + + AppSettings.preLoaderDialog(context); + + final success = + await AppSettings.createAdvanceRequest(payload); + + Navigator.of(context, rootNavigator: true).pop(); + + if (success) { + AppSettings.longSuccessToast("Advance request sent successfully"); + Navigator.pop(context, true); + } else { + AppSettings.longFailedToast("Failed to create account"); + } }, style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF9375E8), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), - minimumSize: Size(double.infinity, 50), + backgroundColor: const Color(0xFF9375E8), + minimumSize: const Size(double.infinity, 50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ), ), - child: Text('Create Account'), + child: const Text('Create Account'), ) ], ), ), ); } -} \ No newline at end of file +} diff --git a/lib/financials/financial_main_screen.dart b/lib/financials/financial_main_screen.dart index 08f3313..2a93967 100644 --- a/lib/financials/financial_main_screen.dart +++ b/lib/financials/financial_main_screen.dart @@ -1,10 +1,10 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/financials/add_transactions.dart'; import 'package:supplier_new/financials/building_transactions_details.dart'; import 'package:supplier_new/financials/create_credit_accounts.dart'; - class FinancialMainScreen extends StatefulWidget { const FinancialMainScreen({super.key}); @@ -22,47 +22,22 @@ Color _amountColor(Map txn) { if (amt.startsWith('-')) return const Color(0xFFE2483D); // red return const Color(0xFF2D2E30); // default/dark text } + class _FinancialMainScreenState extends State with SingleTickerProviderStateMixin { late TabController _tabController; - final transactions = [ - { - "name": "My Home Bhooja", - "date": "21 August", - "amount": "+ ₹2,580", - "color": Colors.green, - "status": "success", - }, - { - "name": "Ramakrishna", - "date": "20 August", - "amount": "- ₹748", - "color": Colors.red, - "status": "success", - }, - { - "name": "Malleshan Water Supplies", - "date": "21 August", - "amount": "- ₹10,000", - "color": Colors.red, - "status": "success", - }, - { - "name": "My Home Bhooja", - "date": "21 August", - "amount": "+ ₹2,580", - "color": Colors.grey, - "status": "failed", - }, - { - "name": "My Home Bhooja", - "date": "21 August", - "amount": "+ ₹2,580", - "color": Colors.green, - "status": "success", - }, - ]; + /// ✅ API transactions + bool isLoadingTransactions = true; + List> transactions = []; + bool isLoadingCreditAccounts = true; + List> creditAccounts = []; + + double receivableBalance = 0; + double advanceBalance = 0; + + int activeAccounts = 0; + int overdueAccounts = 0; @override void initState() { @@ -71,6 +46,77 @@ class _FinancialMainScreenState extends State _tabController.addListener(() { setState(() {}); // rebuilds FAB visibility when tab changes }); + fetchTransactions(); + fetchCreditAccounts(); + } + + Future fetchCreditAccounts() async { + try { + final response = await AppSettings.getAdvanceTransactionsBySupplier(); + final decoded = jsonDecode(response); + + final List list = decoded["data"] ?? []; + + double receivable = 0; + double advance = 0; + + int active = 0; + int overdue = 0; + + // ✅ ADD THIS LIST + final List customerIds = []; + + final accounts = list.map>((e) { + final status = (e["status"] ?? "").toString().toLowerCase(); + + // ✅ COLLECT customerId (IMPORTANT) + if (e["customerId"] != null) { + customerIds.add(e["customerId"].toString()); + } + + // ✅ PARSE advance_amount SAFELY + final double balanceValue = + double.tryParse(e["advance_amount"].toString()) ?? 0; + + // 🔥 MAIN LOGIC CHANGE (ONLY THIS) + if (balanceValue > 0) { + advance += balanceValue; + } else if (balanceValue < 0) { + receivable += balanceValue.abs(); + } + + // existing counters (unchanged) + if (status == "completed") active++; + if (status == "overdue") overdue++; + + return { + "name": e["customerName"] ?? "Unknown", + "id": e["customerId"] ?? "***", + "status": status, + "orders": "${e["orderCount"] ?? 0} orders", + "transactionId": e["transactionId"] ?? "", + "balance": "₹$balanceValue", + "credit": "₹${e["exceed_limit"] ?? 0}", + "lastPayment": + "₹${e["lastPaymentAmount"] ?? 0} | ${e["lastPaymentDate"] ?? ""}", + }; + }).toList(); + + // 🔥 STORE GLOBALLY FOR CREATE CREDIT SCREEN + AppSettings.existingCreditCustomerIds = customerIds; + + setState(() { + creditAccounts = accounts; + receivableBalance = receivable; + advanceBalance = advance; + activeAccounts = active; + overdueAccounts = overdue; + isLoadingCreditAccounts = false; + }); + } catch (e) { + debugPrint("⚠️ Credit accounts error: $e"); + setState(() => isLoadingCreditAccounts = false); + } } @override @@ -79,6 +125,41 @@ class _FinancialMainScreenState extends State super.dispose(); } + /// ✅ FETCH TRANSACTIONS API (based on your response) + Future fetchTransactions() async { + try { + // ⚠️ Use your real method name here + final response = await AppSettings.getSupplierTransactions(); + final decoded = jsonDecode(response); + + final List data = decoded["data"] ?? []; + + transactions = data.map>((e) { + final orderStatus = (e["orderStatus"] ?? "").toString().toLowerCase(); + final price = (e["price"] ?? "0").toString(); + + // completed = credit (+) + // others = debit (-) + final amount = (orderStatus == "completed") ? "+ ₹$price" : "- ₹$price"; + + // cancelled = failed UI + final status = (orderStatus == "cancelled") ? "failed" : "success"; + + return { + "name": (e["buildingName"] ?? e["customerName"] ?? "Unknown").toString(), + "date": (e["dateOfOrder"] ?? "").toString(), + "amount": amount, + "status": status, + "raw": e, // keep full item if you want details page + }; + }).toList(); + + setState(() => isLoadingTransactions = false); + } catch (e) { + setState(() => isLoadingTransactions = false); + } + } + Widget fab() { final index = _tabController.index; @@ -107,26 +188,22 @@ class _FinancialMainScreenState extends State final index = _tabController.index; if (index == 0) { - // Tab 1 → Navigate to Create Account Navigator.push( context, MaterialPageRoute( builder: (_) => AddTransactionScreen(), ), ); - } else if (index == 1) { - // Tab 2 → Navigate to Transactions - /*Navigator.push( - context, - MaterialPageRoute(builder: (_) => const TransactionsPage()), - );*/ } } - Widget Transactions() { + if (isLoadingTransactions) { + return const Center(child: CircularProgressIndicator()); + } + return Container( - color: Color(0XFFFFFFFF), + color: const Color(0XFFFFFFFF), child: Column( children: [ // Filters row @@ -144,39 +221,40 @@ class _FinancialMainScreenState extends State ), Expanded( - child: ListView.builder( + child: transactions.isEmpty?Center( + child: Text( + 'No Data Available', + style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700), + ), + ): + ListView.builder( padding: EdgeInsets.zero, itemCount: transactions.length, itemBuilder: (context, index) { final txn = transactions[index]; return ListTile( - dense: true, // + dense: true, minVerticalPadding: 0, - visualDensity: const VisualDensity(vertical: 0, horizontal: 0), // ✅ tighter - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0), // ✅ no extra top/bottom + visualDensity: const VisualDensity(vertical: 0, horizontal: 0), + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0), horizontalTitleGap: 8, minLeadingWidth: 40, - leading: const CircleAvatar( - radius: 18, - backgroundColor: Colors.blue, - child: Icon(Icons.person, color: Colors.white, size: 18), - ), + leading: Image.asset('images/avatar.png', width: 30, height: 30), title: Text( txn["name"].toString(), - style: fontTextStyle(14, Color(0xFF2D2E30), FontWeight.w500) - .copyWith(height: 1.1), // ✅ tighter line height + style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500) + .copyWith(height: 1.1), maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Text( txn["date"].toString(), - style: fontTextStyle(12, Color(0xFF939495), FontWeight.w400) - .copyWith(height: 1.0), // ✅ tighter line height + style: fontTextStyle(12, const Color(0xFF939495), FontWeight.w400) + .copyWith(height: 1.0), maxLines: 1, overflow: TextOverflow.ellipsis, ), - trailing: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.center, @@ -189,7 +267,7 @@ class _FinancialMainScreenState extends State Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.error, color: Color(0xFF9F9F9F), size: 14), // gray + const Icon(Icons.error, color: Color(0xFF9F9F9F), size: 14), const SizedBox(width: 4), Text( "Failed", @@ -199,59 +277,53 @@ class _FinancialMainScreenState extends State ), ], ), - ); }, ), ) - ], ), ); } Widget CreditAccounts() { + if (isLoadingCreditAccounts) { + return const Center(child: CircularProgressIndicator()); + } + return SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ - // Account Summary - + /// Create Account Button (UNCHANGED) Align( - alignment: Alignment.centerRight, // aligns button to the right + alignment: Alignment.centerRight, child: OutlinedButton( style: OutlinedButton.styleFrom( - foregroundColor: Color(0xFFFFFFFF), - backgroundColor: Color(0xFF000000), - side: BorderSide(color: Color(0xFF000000)), - padding: EdgeInsets.symmetric( - vertical: 10, horizontal: 16), // padding around content + foregroundColor: Colors.white, + backgroundColor: Colors.black, ), onPressed: () { Navigator.push( context, - MaterialPageRoute( - builder: (_) => CreateCreditAccountScreen(), - ), + MaterialPageRoute(builder: (_) => CreateCreditAccountScreen()), ); }, child: Row( mainAxisSize: MainAxisSize.min, - // shrink button to fit content children: [ - Image.asset('images/plus.png', height: 20, width: 20), - SizedBox(width: 4), - Text( - "Create Account", - style: fontTextStyle(12, const Color(0xFFFFFFFF), FontWeight.w400), - ), + Image.asset('images/plus.png', height: 20), + const SizedBox(width: 6), + const Text("Create Account"), ], ), ), ), - // Account Summary (ONLY header + count) + const SizedBox(height: 12), + + /// Account Summary Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( @@ -259,24 +331,17 @@ class _FinancialMainScreenState extends State borderRadius: BorderRadius.circular(16), ), child: Row( - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Expanded( - child: Text( - "Account Summary", - style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w500), - ), - ), + const Expanded(child: Text("Account Summary")), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( - "05", + creditAccounts.length.toString(), style: fontTextStyle(24, const Color(0xFF0D3771), FontWeight.w500), ), - const SizedBox(height: 2), Text( - "4 active, 1 overdue", + "$activeAccounts active, $overdueAccounts overdue", style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400), ), ], @@ -287,143 +352,102 @@ class _FinancialMainScreenState extends State const SizedBox(height: 12), -// NEW: Balances in a separate container/card - Container( - padding: const EdgeInsets.all(16), - - child: Row( - children: [ - Expanded( - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: const Color(0xffFFFFFF), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("Receivable Balance", - style: fontTextStyle(12, Color(0xFF2D2E30), FontWeight.w500)), - const SizedBox(height: 4), - Text("₹24,000", - style: fontTextStyle(16, Color(0xFFE2483D), FontWeight.w500)), - Text("40.6% of total credit", - style: fontTextStyle(10, Color(0xFF646566), FontWeight.w400)), - ], - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: const Color(0xffFFFFFF), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("Advance Balance", - style: fontTextStyle(12, Color(0xFF2D2E30), FontWeight.w500)), - const SizedBox(height: 4), - Text("₹24,000", - style: fontTextStyle(16, Color(0xFFE2483D), FontWeight.w500)), - Text("60.4% of total credit", - style: fontTextStyle(10, Color(0xFF646566), FontWeight.w400)), - ], - ), - ), - ), - ], - ), + /// Balances + Row( + children: [ + _balanceBox( + title: "Receivable Balance", + amount: receivableBalance, + color: Colors.red, + ), + const SizedBox(width: 12), + _balanceBox( + title: "Advance Balance", + amount: advanceBalance, + color: Colors.green, + ), + ], ), const SizedBox(height: 20), - // Search Bar - Container( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - children: [ - const Icon(Icons.search, color: Colors.grey), - const SizedBox(width: 8), - const Expanded( - child: TextField( - decoration: InputDecoration( - hintText: "Search", - border: InputBorder.none, - ), - ), - ), - Image.asset("images/icon_tune.png", width: 24, height: 24), - const SizedBox(width: 16), - Image.asset("images/up_down_arrow.png", width: 24, height: 24), - ], - ), + /// Accounts List + creditAccounts.isEmpty + ? const Center(child: Text("No Credit Accounts")) + : Column( + children: creditAccounts.map((acc) { + return _accountTile( + name: acc["name"] ?? "", + id: acc["id"] ?? "", + status: acc["status"] ?? "", + transactionId: acc["transactionId"] ?? "", // ✅ FIX + orders: acc["orders"] ?? "", + balance: acc["balance"] ?? "₹0", + credit: acc["credit"] ?? "₹0", + lastPayment: acc["lastPayment"] ?? "", + balanceColor: (acc["balanceValue"] ?? 0) < 0 + ? Colors.red + : Colors.green, + creditColor: Colors.red, + ); + }).toList(), ), + ], + ), + ), + ); + } - const SizedBox(height: 20), - - // Accounts List - _accountTile( - name: "SVG Towers", - status: "active", - orders: "48 orders", - balance: "₹10,000", - credit: "₹10,000", - lastPayment: "₹5,000 | 12 Aug", - balanceColor: Colors.red, - creditColor: Colors.green, - ), - _accountTile( - name: "Canvas Creations", - status: "active", - orders: "32 orders", - balance: "₹7,500", - credit: "₹7,500", - lastPayment: "₹3,000 | 5 Aug", - balanceColor: Colors.red, - creditColor: Colors.green, - ), - _accountTile( - name: "Digital Designs", - status: "inactive", - orders: "15 orders", - balance: "₹2,000", - credit: "₹2,000", - lastPayment: "₹1,000 | 1 Aug", - balanceColor: Colors.red, - creditColor: Colors.green, - ), + Widget _balanceBox({ + required String title, + required double amount, + required Color color, + }) { + return Expanded( + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title), + const SizedBox(height: 4), + Text("₹${amount.toStringAsFixed(0)}", + style: TextStyle(color: color, fontSize: 16)), ], ), ), ); } + + Widget _accountTile({ required String name, required String status, + required String id, required String orders, required String balance, required String credit, required String lastPayment, required Color balanceColor, required Color creditColor, + required String transactionId, }) { return GestureDetector( onTap: () { + if (status == "paid_by_customer") return; + Navigator.push( context, MaterialPageRoute( - builder: (_) => BuildingTransactionsDetails(), + builder: (_) => BuildingTransactionsDetails( + supplierId: AppSettings.supplierId, // ✅ from logged-in supplier + customerId: id, // ✅ this account's customer + ), ), ); }, @@ -437,16 +461,43 @@ class _FinancialMainScreenState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Name + Status - Row( children: [ Expanded( child: Text( name, style: fontTextStyle(14, Color(0xFF2D2E30), FontWeight.w500)), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: status == "active" ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( status, style: TextStyle( color: status == "active" ? Colors.green : Colors.redAccent, fontSize: 12, fontWeight: FontWeight.w500, ), ), ) ], ), - const SizedBox(height: 4), - Text( - orders, - style: const TextStyle(color: Colors.grey, fontSize: 12), + Row( + children: [ + Expanded( + child: Text(id, + style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500)), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: status == "completed" + ? Colors.green.withOpacity(0.1) + : Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + status == "completed" + ? "active" + : status == "paid_by_customer" + ? "pending" + : status, + style: TextStyle( + color: status == "completed" + ? Colors.green + : status == "paid_by_customer" + ? Colors.orange + : Colors.redAccent, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + + ) + ], ), + const SizedBox(height: 4), + Text(orders, style: const TextStyle(color: Colors.grey, fontSize: 12)), const SizedBox(height: 12), - - // Balance - Credit - Last Payment Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -456,27 +507,18 @@ class _FinancialMainScreenState extends State const Text("Available Balance", style: TextStyle(fontSize: 12, color: Colors.black54)), const SizedBox(height: 4), - Text( - balance, - style: TextStyle(fontSize: 14, - fontWeight: FontWeight.bold, - color: balanceColor), - ), + Text(balance, + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: balanceColor)), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text("Available Credit", - style: TextStyle( - fontSize: 12, color: Colors.black54)), + style: TextStyle(fontSize: 12, color: Colors.black54)), const SizedBox(height: 4), - Text( - credit, - style: TextStyle(fontSize: 14, - fontWeight: FontWeight.bold, - color: creditColor), - ), + Text(credit, + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: creditColor)), ], ), Column( @@ -485,15 +527,38 @@ class _FinancialMainScreenState extends State const Text("Last Payment", style: TextStyle(fontSize: 12, color: Colors.black54)), const SizedBox(height: 4), - Text( - lastPayment, - style: fontTextStyle( - 12, Color(0XFFF1F1F1), FontWeight.w500), - ), + Text(lastPayment, + style: fontTextStyle(12, const Color(0XFFF1F1F1), FontWeight.w500)), ], ), + ], - ) + ), + // 👇 ADD THIS AT THE END OF children[] + if (status == "paid_by_customer") ...[ + const SizedBox(height: 8), + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () async { + final ok = + await AppSettings.confirmAdvanceReceived( + transactionId, // AWS transactionId + "accept", + ); + + if (ok) { + AppSettings.longSuccessToast("Advance accepted"); + fetchCreditAccounts(); // refresh list + } else { + AppSettings.longFailedToast("Failed to accept"); + } + }, + child: const Text("Accept"), + ), + ), + ], + ], ), ), @@ -503,6 +568,7 @@ class _FinancialMainScreenState extends State @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: Color(0XFFFFFFFF), appBar: AppBar( elevation: 0, backgroundColor: Colors.white, @@ -517,27 +583,22 @@ class _FinancialMainScreenState extends State return TabBar( controller: _tabController, indicatorColor: Colors.transparent, - // remove underline dividerColor: Colors.transparent, isScrollable: false, overlayColor: MaterialStateProperty.all(Colors.transparent), - // equal width tabs: List.generate(2, (index) { final labels = ['Transactions', 'Credit Accounts']; final isSelected = _tabController.index == index; return Container( decoration: BoxDecoration( - color: isSelected ? const Color(0XFFF1F1F1) : Colors - .transparent, + color: isSelected ? const Color(0XFFF1F1F1) : Colors.transparent, borderRadius: BorderRadius.circular(27), ), alignment: Alignment.center, child: Text( labels[index], - style: fontTextStyle( - 12, Color(0XFF000000), FontWeight.w600, - ), + style: fontTextStyle(12, const Color(0XFF000000), FontWeight.w600), ), ); }), @@ -550,15 +611,10 @@ class _FinancialMainScreenState extends State body: TabBarView( controller: _tabController, children: [ - // Tab 1: Transactions Transactions(), - - // Tab 2: Credit Accounts - CreditAccounts() + CreditAccounts(), ], ), - - floatingActionButton: _tabController.index == 0 ? fab() : null, ); } @@ -579,11 +635,7 @@ class _FinancialMainScreenState extends State ), ), const SizedBox(width: 4), - const Icon( - Icons.keyboard_arrow_down, // ⬇️ Down arrow - size: 18, - color: Colors.black, - ), + const Icon(Icons.keyboard_arrow_down, size: 18, color: Colors.black), ], ), backgroundColor: Colors.white, @@ -592,10 +644,8 @@ class _FinancialMainScreenState extends State shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), - onSelected: (val) { - }, + onSelected: (val) {}, ), ); } - -} \ No newline at end of file +} diff --git a/lib/plans/accept_plan_requests.dart b/lib/plans/accept_plan_requests.dart index 71d0a57..57cca85 100644 --- a/lib/plans/accept_plan_requests.dart +++ b/lib/plans/accept_plan_requests.dart @@ -15,12 +15,160 @@ class _AcceptPlanRequestsState extends State { //int advance =0; double amountToPayAfterDelivery = 0.0; + String paymentMode = "after_delivery"; // advance | credit | after_delivery + TextEditingController advanceAmountCtrl = TextEditingController(); + TextEditingController creditLimitCtrl = TextEditingController(); + final TextEditingController tankerPriceController = TextEditingController(); + + final List> paymentTypes = [ + {"key": "advance", "label": "Advance"}, + {"key": "after_delivery", "label": "After Delivery"}, + {"key": "credit", "label": "Credit"}, + ]; + + String? advanceError; + String? creditError; + @override void initState() { // TODO: implement initState + tankerPriceController.text = '${widget.order.quoted_amount}'; super.initState(); } + Future showAdvanceSelectionBottomSheet(BuildContext context) async { + await showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (ctx) { + return StatefulBuilder( + builder: (context, setStateSB) { + return Container( + padding: EdgeInsets.fromLTRB( + 16, 16, 16, MediaQuery.of(context).viewInsets.bottom + 16), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey[400], + borderRadius: BorderRadius.circular(2), + ), + ), + ), + const SizedBox(height: 16), + Text( + "SELECT PAYMENT MODE", + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), + ), + RadioListTile( + title: Text("Advance", + style: + fontTextStyle(14, Colors.black, FontWeight.w400)), + value: "advance", + groupValue: paymentMode, + onChanged: (v) { + setStateSB(() => paymentMode = v!); + }, + ), + if (paymentMode == "advance") + TextField( + controller: advanceAmountCtrl, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + hintText: "Enter advance amount", + ), + ), + RadioListTile( + title: Text("After Delivery", + style: + fontTextStyle(14, Colors.black, FontWeight.w400)), + value: "after_delivery", + groupValue: paymentMode, + onChanged: (v) { + setStateSB(() => paymentMode = v!); + }, + ), + RadioListTile( + title: Text("Credit", + style: + fontTextStyle(14, Colors.black, FontWeight.w400)), + value: "credit", + groupValue: paymentMode, + onChanged: (v) { + setStateSB(() => paymentMode = v!); + }, + ), + if (paymentMode == "credit") + TextField( + controller: creditLimitCtrl, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + hintText: "Enter credit limit", + ), + ), + const SizedBox(height: 12), + ElevatedButton( + onPressed: () { + // 🔐 VALIDATION AT SELECTION TIME + if (paymentMode == "advance" && + advanceAmountCtrl.text.trim().isEmpty) { + AppSettings.longFailedToast("Please enter advance amount"); + return; + } + + if (paymentMode == "credit" && + creditLimitCtrl.text.trim().isEmpty) { + AppSettings.longFailedToast("Please enter credit limit"); + return; + } + + // ✅ Valid → update main screen & close + setState(() {}); + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0XFF0A9E04), + foregroundColor: Color(0XFFFFFFFF), + padding: EdgeInsets.symmetric(vertical: 10,horizontal: 16), + ), + child: Text( + "Confirm", + style: fontTextStyle( + 14, const Color(0XFFFFFFFF), FontWeight.w400), + ), + ), + ], + ), + ); + }, + ); + }, + ); + } + + bool isPaymentValid() { + if (paymentMode == "advance") { + return advanceAmountCtrl.text.trim().isNotEmpty; + } + + if (paymentMode == "credit") { + return creditLimitCtrl.text.trim().isNotEmpty; + } + + return true; // after_delivery + } + @override Widget build(BuildContext context) { return Scaffold( @@ -177,13 +325,74 @@ class _AcceptPlanRequestsState extends State { SizedBox( height: MediaQuery.of(context).size.height * .011, ), - _detailTwoRow( + _twoFields(tankerPriceController, "Tanker Price", + 'images/price.png', false, null, null, null, null), + Container( + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: const Color(0XFFFFFFFF), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0XFFC3C4C4)), + ), + child: Padding( + padding: const EdgeInsets.all(4), + child: GestureDetector( + onTap: () => showAdvanceSelectionBottomSheet(context), + behavior: HitTestBehavior.opaque, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // LEFT CONTENT + Expanded( + child: Stack( + alignment: Alignment.centerRight, + children: [ + _detailTwoRow( + "Payment Type", + paymentMode == "advance" + ? "Advance - ₹${advanceAmountCtrl.text.isEmpty ? "0" : advanceAmountCtrl.text}" + : paymentMode == "credit" + ? "Credit - ₹${creditLimitCtrl.text.isEmpty ? "0" : creditLimitCtrl.text}" + : "After Delivery", + "images/advance.png", + "", + "", + "", + ), + + // ⬇️ Arrow FIXED near text + Padding( + padding: const EdgeInsets.only(right: 8), + child: Image.asset( + 'images/downarrow.png', + height: 18, + width: 18, + color: const Color(0XFFC3C4C4), + ), + ), + ], + ), + ), + + // ✏ Edit icon at far right + Image.asset( + 'images/edit.png', + height: 18, + width: 18, + color: const Color(0XFFC3C4C4), + ), + ], + ), + ), + ), + ), + /* _detailTwoRow( "Tanker Price", "₹${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}", "images/financialsBottomIcon.png", "", "", - ""), + ""),*/ SizedBox( height: MediaQuery.of(context).size.height * .02, ), @@ -194,41 +403,30 @@ class _AcceptPlanRequestsState extends State { "Capacity", "${widget.order.capacity}", "images/capacity.png", - ), SizedBox( height: MediaQuery.of(context).size.height * .02, ), _detailTwoRow( "Start Date", - "${widget.order.averageTime}", + "${widget.order.startDate}", "images/time.png", "End Date", - "${widget.order.averageTime}", - "images/advance.png", + "${widget.order.endDate}", + "images/time.png", ), SizedBox( height: MediaQuery.of(context).size.height * .02, ), - _detailTwoRow( - "Frequency", - "${widget.order.quantity}", - "images/quantity.png", - "", - "", - ""), - - + _detailTwoRow("Frequency", "${widget.order.quantity}", + "images/quantity.png", "", "", ""), ], ), ), - SizedBox( - height: MediaQuery.of(context).size.height * .008, - ), /// 🔹 Additional Details Padding( - padding: EdgeInsets.fromLTRB(16, 0, 16, 16), + padding: EdgeInsets.fromLTRB(16, 16, 16, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -240,14 +438,8 @@ class _AcceptPlanRequestsState extends State { SizedBox( height: MediaQuery.of(context).size.height * .011, ), - Text( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " - "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " - "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " - "aliquip ex ea commodo consequat.", - style: fontTextStyle( - 12, const Color(0XFF646566), FontWeight.w400), - ), + Text('Total tankers for this plan are ${widget.order.dates.length}', style: fontTextStyle( + 12, const Color(0xFF939495), FontWeight.w400),) ], )), @@ -265,9 +457,31 @@ class _AcceptPlanRequestsState extends State { SizedBox( height: MediaQuery.of(context).size.height * .011, ), - _detailRow("Tanker Price", - "₹${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}"), - /* SizedBox( + ValueListenableBuilder( + valueListenable: tankerPriceController, + builder: (context, value, _) { + final int tankerCount = widget.order.dates.length; + final int price = + int.tryParse(value.text.isEmpty ? "0" : value.text) ?? 0; + + return Text.rich( + TextSpan( + children: [ + TextSpan( + text: "Total amount for all plan orders ", + style: fontTextStyle( + 12, const Color(0xFF939495), FontWeight.w400)), + TextSpan( + text: '$tankerCount × $price = ${tankerCount * price}', + style: fontTextStyle( + 12, const Color(0xFF515253), FontWeight.w500)), + ], + ), + textAlign: TextAlign.center, + ); + }, + ), + /* SizedBox( height: MediaQuery.of(context).size.height * .004, ), @@ -310,14 +524,13 @@ class _AcceptPlanRequestsState extends State { Expanded( child: OutlinedButton( style: OutlinedButton.styleFrom( - foregroundColor: Color(0XFF000000), - backgroundColor: Color(0xFFF1F1F1), - side: BorderSide(color: Color(0xFFF1F1F1)), - padding: - EdgeInsets.symmetric(vertical: 10), // uniform height + foregroundColor: Color(0XFFE2483D), + backgroundColor: Colors.white, + side: BorderSide(color: Color(0XFFE2483D)), + padding: EdgeInsets.symmetric(vertical: 10), ), onPressed: () { - Navigator.push( + /*Navigator.push( context, MaterialPageRoute( builder: (_) => EditPlanRequests( @@ -325,15 +538,16 @@ class _AcceptPlanRequestsState extends State { status: widget.status, ), ), - ); + );*/ + Navigator.pop(context); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Image.asset('images/edit.png', height: 20, width: 20), + Image.asset('images/cross.png', height: 20, width: 20), SizedBox(width: 4), Text( - "Edit Plan", + "Cancel", style: fontTextStyle( 14, const Color(0XFF000000), FontWeight.w400), ), @@ -345,12 +559,12 @@ class _AcceptPlanRequestsState extends State { Expanded( child: OutlinedButton( style: OutlinedButton.styleFrom( - foregroundColor: Color(0XFFE2483D), - backgroundColor: Colors.white, + foregroundColor: Color(0XFFFFFFFF), + backgroundColor: Color(0XFFE2483D), side: BorderSide(color: Color(0XFFE2483D)), padding: EdgeInsets.symmetric(vertical: 10), ), - onPressed: ()async { + onPressed: () async { /*AppSettings.preLoaderDialog(context); bool isOnline = await AppSettings.internetConnectivity(); @@ -390,12 +604,12 @@ class _AcceptPlanRequestsState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Image.asset('images/cross.png', height: 20, width: 20), + Image.asset('images/cross.png', height: 20, width: 20,color: Color(0XFFFFFFFF),), SizedBox(width: 4), Text( "Reject", style: fontTextStyle( - 14, const Color(0XFF000000), FontWeight.w400), + 14, const Color(0XFFFFFFFF), FontWeight.w400), ), ], ), @@ -414,33 +628,57 @@ class _AcceptPlanRequestsState extends State { bool isOnline = await AppSettings.internetConnectivity(); - if (isOnline) { - var payload = new Map(); - /*payload["supplierId"] = AppSettings.supplierId; - payload["amount"] = int.parse(widget.order.quoted_amount); - payload["delivery_charges"] = advance;*/ - payload["action"] = 'accept'; + if (!isOnline) { + Navigator.of(context, rootNavigator: true).pop(); + AppSettings.longFailedToast("Please Check internet"); + return; + } + + if (!isPaymentValid()) { + Navigator.of(context, rootNavigator: true).pop(); + AppSettings.longFailedToast( + paymentMode == "advance" + ? "Please enter advance amount" + : "Please enter credit limit", + ); + return; + } + + try { + final Map payload = {}; + + payload["action"] = "accept"; + payload["actual_price"] = int.parse(widget.order.quoted_amount); + payload["bidding_price"] = int.parse(tankerPriceController.text); + + if (paymentMode == "advance") { + payload["payment_type"] = "advance"; + payload["adavnce_amount"] = + int.tryParse(advanceAmountCtrl.text) ?? 0; + } else if (paymentMode == "credit") { + payload["payment_type"] = "credit"; + payload["adavnce_amount"] = + int.tryParse(creditLimitCtrl.text) ?? 0; + } else { + payload["payment_type"] = "after_delivery"; + payload["adavnce_amount"] = 0; + } bool status = await AppSettings.acceptPlanRequests( - payload, widget.order.dbId); + payload, + widget.order.dbId, + ); - try { - if (status) { - Navigator.of(context,rootNavigator: true).pop(); - Navigator.pop(context, true); - } - else{ - Navigator.of(context,rootNavigator: true).pop(); - AppSettings.longFailedToast("Accept of plan request Failed"); - } - } catch (e) { - Navigator.of(context,rootNavigator: true).pop(); - print(e); + Navigator.of(context, rootNavigator: true).pop(); + + if (status) { + Navigator.pop(context, true); + } else { + AppSettings.longFailedToast("Accept of plan request Failed"); } - } - else{ - Navigator.of(context,rootNavigator: true).pop(); - AppSettings.longFailedToast("Please Check internet"); + } catch (e) { + Navigator.of(context, rootNavigator: true).pop(); + AppSettings.longFailedToast("Something went wrong"); } }, child: Row( @@ -462,6 +700,107 @@ class _AcceptPlanRequestsState extends State { ); } + Widget _textField(TextEditingController? controller, String? label, + String? path, bool readOnly) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + child: TextField( + controller: controller, + cursorColor: primaryColor, + readOnly: readOnly, + decoration: InputDecoration( + counterText: '', + filled: false, + fillColor: Colors.white, + prefixIcon: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0), + child: SizedBox( + width: 18, + height: 18, + child: Image.asset( + path!, + fit: BoxFit.contain, + color: Color(0XFFC3C4C4), + ), + ), + ), + prefixIconConstraints: BoxConstraints( + minWidth: 24, + minHeight: 24, + ), + suffixIcon: readOnly + ? null + : Padding( + padding: const EdgeInsets.only(right: 8), + child: Image.asset( + 'images/edit.png', + height: 18, + width: 18, + color: const Color(0XFFC3C4C4), + ), + ), + suffixIconConstraints: BoxConstraints( + minWidth: 24, + minHeight: 24, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0), + borderSide: BorderSide( + color: Color(0XFFC3C4C4), + width: 1, + )), + focusedBorder: !readOnly + ? OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0), + borderSide: BorderSide( + color: Color(0XFF8270DB), + width: 1, + ), + ) + : OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0), + borderSide: BorderSide( + color: Color(0XFFC3C4C4), + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0), + borderSide: BorderSide(color: Color(0XFFC3C4C4)), + ), + labelText: label, + labelStyle: fontTextStyle(12, Color(0XFF646566), FontWeight.w400), + /* TextStyle(color: greyColor, fontWeight: FontWeight.bold //<-- SEE HERE + ),*/ + ), + style: fontTextStyle(12, Color(0XFF343637), FontWeight.w500), + ), + ); + } + + Widget _twoFields( + TextEditingController? controller1, + String? label1, + String? path1, + bool? readOnly1, + TextEditingController? controller2, + String? label2, + String? path2, + bool? readOnly2) { + return Row( + children: [ + Expanded( + child: _textField(controller1, label1, path1, readOnly1!), + ), + const SizedBox(width: 10), + if (controller2 != null) + Expanded( + child: _textField(controller2, label2, path2, readOnly2!), + ), + ], + ); + } + /// 🔹 Helper widget for rows Widget _detailRow(String title, String value) { return Padding( @@ -587,17 +926,19 @@ class _AcceptPlanRequestsState extends State { const SizedBox(height: 20), Text( "REASON FOR REJECTION", - style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600), + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), ), const SizedBox(height: 8), // reasons ...reasons.map( - (reason) => RadioListTile( + (reason) => RadioListTile( contentPadding: EdgeInsets.zero, title: Text( reason, - style: fontTextStyle(14, const Color(0XFF2D2E30), FontWeight.w400), + style: fontTextStyle( + 14, const Color(0XFF2D2E30), FontWeight.w400), ), value: reason, activeColor: const Color(0XFFE2483D), @@ -614,14 +955,15 @@ class _AcceptPlanRequestsState extends State { onPressed: () => Navigator.pop(context, null), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), - side: const BorderSide(color:Color(0XFFFFFFFF)), + side: const BorderSide(color: Color(0XFFFFFFFF)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), ), child: Text( "Cancel", - style: fontTextStyle(14, const Color(0XFF939495), FontWeight.w600), + style: fontTextStyle( + 14, const Color(0XFF939495), FontWeight.w600), ), ), ), @@ -641,7 +983,8 @@ class _AcceptPlanRequestsState extends State { ), child: Text( "Reject Request", - style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600), + style: fontTextStyle( + 14, const Color(0XFFFFFFFF), FontWeight.w600), ), ), ), @@ -656,5 +999,4 @@ class _AcceptPlanRequestsState extends State { }, ); } - } diff --git a/lib/plans/all_plans.dart b/lib/plans/all_plans.dart index 9be6d2f..55c57d9 100644 --- a/lib/plans/all_plans.dart +++ b/lib/plans/all_plans.dart @@ -1,16 +1,94 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/plans/plan_details.dart'; import 'package:supplier_new/plans/plan_requests.dart'; -import 'package:supplier_new/plans/plans_model.dart'; import 'package:supplier_new/plans/search_plan_appbar.dart'; +/// ===================== +/// MODEL +/// ===================== +class PlansModel { + final String id; + final String status; + final String apartment; + final String liters; + final String price; + final String advance; + final String deliveries; + final String frequency; + final String waterType; + + PlansModel({ + required this.id, + required this.status, + required this.apartment, + required this.liters, + required this.price, + required this.advance, + required this.deliveries, + required this.frequency, + required this.waterType, + }); + + factory PlansModel.fromJson(Map json) { + final supplier = json["my_supplier"] ?? {}; + final List dates = json["dates"] ?? []; + + return PlansModel( + id: json["_id"], + status: json["status"] == "processed" ? "Active" : "Pending", + apartment: json["customerId"], + liters: "${json["capacity"]} - ${json["type_of_water"]}", + price: "₹${supplier["quoted_amount"] ?? "--"}", + advance: "--", + deliveries: "${dates.length} Deliveries", + frequency: "${json["weekly_count"]}/week", + waterType: json["type_of_water"] ?? "", + ); + } +} + +/// ===================== +/// SCREEN +/// ===================== +/// +Future showAcceptConfirmDialog(BuildContext context) async { + return await showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + title: const Text("Confirm Acceptance"), + content: const Text( + "Are you sure you want to accept this recurring plan?", + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text("Cancel"), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0XFF8270DB), + ), + onPressed: () => Navigator.pop(ctx, true), + child: const Text("Accept"), + ), + ], + ); + }, + ) ?? + false; +} + +/// class AllPlans extends StatefulWidget { final String navigationFrom; - AllPlans({ - super.key, - required this.navigationFrom, - }); + const AllPlans({super.key, required this.navigationFrom}); @override State createState() => _AllPlansState(); @@ -19,60 +97,85 @@ class AllPlans extends StatefulWidget { class _AllPlansState extends State { final TextEditingController searchController = TextEditingController(); + bool isLoading = true; + + /// UI MODELS + List plans = []; + + /// RAW API DATA (IMPORTANT) + List> rawPlans = []; + + int activeCount = 0; + int boreCount = 0; + int drinkingCount = 0; + + @override + void initState() { + super.initState(); + fetchPlans(); + } + + + + + + /// ===================== + /// FETCH API + /// ===================== + Future fetchPlans() async { + try { + final response = await AppSettings.getAcceptedRecurringBookings(); + final decoded = jsonDecode(response); + + final List data = decoded["data"] ?? []; + + /// ✅ FILTER ONLY payment_completed + rawPlans = List>.from( + data.where((e) => + ["processed", "payment_completed"] + .contains((e["status"] ?? "").toString().toLowerCase())), + ); + + plans = rawPlans.map((e) => PlansModel.fromJson(e)).toList(); + + /// COUNTS (NOW ONLY payment_completed DATA) + activeCount = plans.length; + + boreCount = + plans.where((p) => p.waterType.toLowerCase().contains("bore")).length; + + drinkingCount = plans + .where((p) => p.waterType.toLowerCase().contains("drinking")) + .length; + + setState(() => isLoading = false); + } catch (e) { + setState(() => isLoading = false); + } + } + + /// ===================== + /// UI + /// ===================== @override Widget build(BuildContext context) { - final List plans = [ - PlansModel( - status: "Active", - apartment: "Green Valley Apartments", - liters: "10,000 L - Drinking water", - price: "₹3,400", - advance: "10% Advance", - deliveries: "10/22 Deliveries", - frequency: "4/week", - ), - PlansModel( - status: "Active", - apartment: "Lakeview Towers", - liters: "8,000 L - Drinking water", - price: "₹2,900", - advance: "5% Advance", - deliveries: "8/20 Deliveries", - frequency: "3/week", - ), - PlansModel( - status: "Active", - apartment: "Hilltop Residency", - liters: "12,000 L - Drinking water", - price: "₹4,500", - advance: "15% Advance", - deliveries: "12/25 Deliveries", - frequency: "5/week", - ), - PlansModel( - status: "Inactive", - apartment: "Silver Oak Villas", - liters: "6,000 L - Drinking water", - price: "₹2,100", - advance: "0% Advance", - deliveries: "0/15 Deliveries", - frequency: "0/week", - ), - ]; + return Scaffold( - backgroundColor: const Color(0XFFFFFFFF), - appBar: widget.navigationFrom.toString().toLowerCase()=='dashboard'?SearchPlanAppBar( + backgroundColor: Colors.white, + appBar: widget.navigationFrom.toLowerCase() == 'dashboard' + ? SearchPlanAppBar( controller: searchController, onBack: () => Navigator.pop(context), - onHelp: () { - print("Help tapped"); - }, - ):null, - body: SingleChildScrollView( + onHelp: () {}, + ) + : null, + body: isLoading + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( child: Column( children: [ - /// 🔹 Grey top section + /// 🔹 TOP SECTION Container( decoration: const BoxDecoration( color: Color(0XFFF2F2F2), @@ -81,48 +184,42 @@ class _AllPlansState extends State { bottomRight: Radius.circular(24), ), ), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + padding: const EdgeInsets.all(24), child: Column( children: [ Text( - "05", - style: fontTextStyle(64, const Color(0XFF2D2E30), FontWeight.w700), + activeCount.toString().padLeft(2, '0'), + style: fontTextStyle( + 64, const Color(0XFF2D2E30), FontWeight.w700), ), Text( "Active Plans", - style: fontTextStyle(24, const Color(0XFF2D2E30), FontWeight.w600), + style: fontTextStyle( + 24, const Color(0XFF2D2E30), FontWeight.w600), ), const SizedBox(height: 24), - /// Bore Water + Drinking Water Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ PlanCategoryCard( - image: Image.asset( - 'images/bore-water.png', - fit: BoxFit.contain, - height: 40, - width: 40, - ), - value: "02", + image: Image.asset('images/bore-water.png', + height: 40, width: 40), + value: boreCount.toString().padLeft(2, '0'), label: "Bore Water", ), PlanCategoryCard( - image: Image.asset( - 'images/drinking-water.png', - height: 40, - width: 40, - fit: BoxFit.contain, - ), - value: "03", + image: Image.asset('images/drinking-water.png', + height: 40, width: 40), + value: + drinkingCount.toString().padLeft(2, '0'), label: "Drinking Water", ), ], ), + const SizedBox(height: 24), - /// Button SizedBox( width: double.infinity, child: ElevatedButton( @@ -131,20 +228,20 @@ class _AllPlansState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(32), ), - padding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 16), + padding: const EdgeInsets.all(16), ), onPressed: () { Navigator.push( context, MaterialPageRoute( - builder: (context) => const PlanRequestsPage()), + builder: (_) => + const PlanRequestsPage()), ); }, child: Text( "View Plan Requests", style: fontTextStyle( - 16, const Color(0XFFFFFFFF), FontWeight.w500), + 16, Colors.white, FontWeight.w500), ), ), ), @@ -152,48 +249,39 @@ class _AllPlansState extends State { ), ), + /// 🔹 PLAN LIST - /// 🔹 White bottom section (filters + list) - Container( - color: Color(0XFFFFFFFF), - child: Column( - children: [ - const SizedBox(height: 12), - - /// Filters Row - SingleChildScrollView( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - children: [ - FilterChipWidget(label: "Building"), - const SizedBox(width: 8), - FilterChipWidget(label: "Status"), - const SizedBox(width: 8), - FilterChipWidget(label: "Date"), - const SizedBox(width: 8), - FilterChipWidget(label: "Amount"), - ], - ), - ), - - const SizedBox(height: 20), - - /// Order List - ListView.builder( - padding: const EdgeInsets.all(12), - itemCount: plans.length, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - final delivery = plans[index]; - return PlansCard(delivery: delivery); - }, - ), - ], + isLoading + ? Center( + child: CircularProgressIndicator()) + : (plans.isEmpty + ? Center( + child: Padding( + padding: + const EdgeInsets.symmetric( + vertical: 50), + child: Text( + 'No Data Available', + style: fontTextStyle( + 12, + const Color(0XFF2D2E30), + FontWeight.w500), + ), ), - ), + ) + : ListView.builder( + padding: const EdgeInsets.all(12), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: plans.length, + itemBuilder: (_, index) { + return PlansCard( + delivery: plans[index], + planJson: rawPlans[index], // ✅ FIXED + ); + }, + )), ], ), ), @@ -201,219 +289,205 @@ class _AllPlansState extends State { } } -/// Curve clipper for smooth transition -class SmallCurveClipper extends CustomClipper { - @override - Path getClip(Size size) { - final path = Path(); - path.lineTo(0, 0); - path.lineTo(0, size.height); - - // Smooth downward curve - path.quadraticBezierTo( - size.width / 2, -20, // control point (curve depth) - size.width, size.height, - ); - - path.lineTo(size.width, 0); - path.close(); - return path; - } - - @override - bool shouldReclip(covariant CustomClipper oldClipper) => false; -} - -/// Category Card -class PlanCategoryCard extends StatelessWidget { - final Image image; - final String value; - final String label; - - const PlanCategoryCard({ - 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), - const SizedBox(height: 8), - Text( - value, - style: fontTextStyle(20, const Color(0XFF515253), FontWeight.w700), - ), - const SizedBox(height: 4), - Text( - label, - style: fontTextStyle(16, const Color(0XFF515253), FontWeight.w400), - ), - ], - ); - } -} - -/// Filter Chip -class FilterChipWidget extends StatelessWidget { - final String label; - const FilterChipWidget({super.key, required this.label}); - - @override - Widget build(BuildContext context) { - return ChoiceChip( - label: Text(label), - selected: false, - onSelected: (_) {}, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: Colors.white, - side: BorderSide(color: Colors.grey.shade300), - ); - } -} - -/// Plan Card +/// ===================== +/// PLAN CARD +/// ===================== class PlansCard extends StatelessWidget { final PlansModel delivery; + final Map planJson; - const PlansCard({super.key, required this.delivery}); + const PlansCard({ + super.key, + required this.delivery, + required this.planJson, + }); @override Widget build(BuildContext context) { bool isActive = delivery.status == "Active"; return GestureDetector( - onTap: (){ - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PlanDetails(), - ), - ); - }, + onTap: () { + if (delivery.status != "Active") return; + + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => PlanDetails(plan: planJson), + ), + ); + }, child: Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFE5E5E5)), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.03), - blurRadius: 5, - offset: const Offset(0, 2), - ) - ], + color: Colors.white, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Status Chip - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: isActive ? const Color(0xFFE9F9EE) : const Color(0xFFF2F2F2), - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: isActive ? const Color(0xFF3BB273) : Colors.grey.shade400, - ), - ), - child: Text( - delivery.status, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: isActive ? const Color(0xFF3BB273) : Colors.grey.shade600, - ), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: isActive ? const Color(0xFFE9F9EE) : Colors.grey.shade200, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + delivery.status, + style: TextStyle( + fontSize: 12, + color: isActive + ? const Color(0xFF3BB273) + : Colors.grey, ), ), - const SizedBox(height: 8), + ), + const SizedBox(height: 8), - // Apartment + Price Row - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - delivery.apartment, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(delivery.apartment, style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Color(0xFF2A2A2A), - ), - ), - Text( - delivery.price, + fontSize: 15, fontWeight: FontWeight.w600)), + Text(delivery.price, style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Color(0xFF2A2A2A), - ), - ), - ], - ), - const SizedBox(height: 4), + fontSize: 15, fontWeight: FontWeight.w600)), + ], + ), + const SizedBox(height: 4), - // Liters & Advance - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - delivery.liters, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(delivery.liters, style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Color(0xFF7B5AF4), - ), - ), - Text( - delivery.advance, - style: const TextStyle( - fontSize: 12, - color: Color(0xFF646566), + fontSize: 14, color: Color(0xFF7B5AF4))), + Text(delivery.advance, + style: const TextStyle(fontSize: 12)), + ], + ), + Visibility( + visible: delivery.status=='payment_completed', + child: Row( + children: [ + GestureDetector( + onTap: (){ + + }, + child: + ColorFiltered( + colorFilter: ColorFilter.mode( + Colors.transparent, + BlendMode.multiply), + child: Image + .asset( + 'images/wrong_button.png', + width: + 44, + height: + 44, ), ), - ], - ), - const SizedBox(height: 12), + ), + SizedBox( + width: MediaQuery.of(context) + .size + .width * + .012, + ), + GestureDetector( + onTap: () async { + final confirmed = await showAcceptConfirmDialog(context); + if (!confirmed) return; - // Deliveries row with background - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - decoration: BoxDecoration( - color: const Color(0xFFF8F6FF), - borderRadius: BorderRadius.circular(8), + try { + final res = await AppSettings.respondRecurringBooking( + bookingId: planJson["_id"], + supplierId: planJson["my_supplier"]["supplierId"], + action: "accept", + ); + + final List dates = res["data"]["dates"] ?? []; + + if (dates.isEmpty) return; + + /* Navigator.push( + context, + MaterialPageRoute( + builder: (_) => PlanCalendarScreen(dates: dates), + ), + );*/ + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("Failed to accept plan")), + ); + } + }, + child: Image.asset( + 'images/right_button.png', + width: 44, + height: 44, + ), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - delivery.deliveries, - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Color(0xFF2A2A2A), - ), - ), - Text( - delivery.frequency, + + + ], + ),), + + const SizedBox(height: 12), + + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: const Color(0xFFF8F6FF), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(delivery.deliveries, + style: const TextStyle(fontSize: 13)), + Text(delivery.frequency, style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Color(0xFF7B5AF4), - ), - ), - ], - ), + fontSize: 13, color: Color(0xFF7B5AF4))), + ], ), - ], - ), + ), + ]), ), ); } } + +/// ===================== +/// CATEGORY CARD +/// ===================== +class PlanCategoryCard extends StatelessWidget { + final Image image; + final String value; + final String label; + + const PlanCategoryCard({ + 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), + const SizedBox(height: 8), + Text(value, + style: + fontTextStyle(20, const Color(0XFF515253), FontWeight.w700)), + Text(label, + style: + fontTextStyle(16, const Color(0XFF515253), FontWeight.w400)), + ], + ); + } +} diff --git a/lib/plans/calendar.dart b/lib/plans/calendar.dart new file mode 100644 index 0000000..711e38d --- /dev/null +++ b/lib/plans/calendar.dart @@ -0,0 +1,682 @@ +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; + + @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) { + 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 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 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); + // _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 + 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, + ), + ), + ] + ), + ); + }, + ), + + ), + ), + ], + ), + ); + } +} diff --git a/lib/plans/plan_details.dart b/lib/plans/plan_details.dart index 4ce608b..e1deedc 100644 --- a/lib/plans/plan_details.dart +++ b/lib/plans/plan_details.dart @@ -2,76 +2,90 @@ import 'package:flutter/material.dart'; import '../common/settings.dart'; class PlanDetails extends StatefulWidget { - const PlanDetails({super.key}); + final Map plan; // 🔥 pass full plan object + + const PlanDetails({super.key, required this.plan}); @override State createState() => _PlanDetailsState(); } class _PlanDetailsState extends State { - final List> deliveries = [ - { - "status": "Pending", - "quantity": "10,000 L", - "type": "Drinking water", - "time": "12:30 AM, Tomorrow", - "button": "Assign Tanker", - }, - { - "status": "Pending", - "quantity": "10,000 L", - "type": "Drinking water", - "time": "12:30 AM, Tomorrow", - "driver": "TS 04 J 8394", - "button": "Assign Driver", - }, - ]; + late List deliveryDates; + + @override + void initState() { + super.initState(); + deliveryDates = (widget.plan["dates"] as List) + .map((d) => DateTime.parse(d)) + .toList(); + } - Widget _buildInfoCard(String value, String label, {Color? color}) { + // ================= INFO CARD ================= + Widget _buildInfoCard(String value, String label, {Color? bg}) { return Expanded( child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: color ?? const Color(0xFFF7F7F7), + color: bg ?? const Color(0xFFF7F7F7), borderRadius: BorderRadius.circular(12), ), child: Column( children: [ - Text(value, style: fontTextStyle(18, Colors.black, FontWeight.w600)), + Text(value, + style: fontTextStyle(18, Colors.black, FontWeight.w600)), const SizedBox(height: 4), - Text(label, style: fontTextStyle(13, Colors.black54, FontWeight.w400)), + Text(label, + style: + fontTextStyle(13, Colors.black54, FontWeight.w400)), ], ), ), ); } - Widget _buildFilterChip(String label) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - border: Border.all(color: const Color(0xFFE0E0E0)), - borderRadius: BorderRadius.circular(16), - color: Colors.white, - ), - child: Text( - label, - style: fontTextStyle(13, Colors.black87, FontWeight.w500), - ), - ), - ); - } + // ================= DELIVERY CARD ================= + Widget _buildDeliveryCard(DateTime date) { + final now = DateTime.now(); + String status; + String buttonText = ""; + bool showButton = true; + + if (date.isBefore(now)) { + status = "completed"; + showButton = false; + } else if (date.difference(now).inHours < 24) { + status = "in-progress"; + buttonText = "Track Order"; + } else { + status = "pending"; + buttonText = "Assign Tanker"; + } + + Color statusBg; + Color statusText; + + switch (status) { + case "completed": + statusBg = Colors.green.withOpacity(0.15); + statusText = Colors.green; + break; + case "in-progress": + statusBg = Colors.blue.withOpacity(0.15); + statusText = Colors.blue; + break; + default: + statusBg = Colors.orange.withOpacity(0.15); + statusText = Colors.orange; + } - Widget _buildDeliveryCard(Map delivery) { return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white, border: Border.all(color: const Color(0xFFE0E0E0)), borderRadius: BorderRadius.circular(12), + color: Colors.white, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -82,62 +96,60 @@ class _PlanDetailsState extends State { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFFFFF2E0), + color: statusBg, borderRadius: BorderRadius.circular(8), ), child: Text( - delivery['status'], - style: fontTextStyle( - 12, const Color(0xFFE6882C), FontWeight.w500), + status, + style: fontTextStyle(12, statusText, FontWeight.w600), ), ), const Spacer(), - Text(delivery['time'], - style: fontTextStyle(12, Colors.black54, FontWeight.w400)), + Text( + "${date.day}-${date.month}-${date.year}", + style: + fontTextStyle(12, Colors.black54, FontWeight.w400), + ), ], ), const SizedBox(height: 8), - Text("${delivery['quantity']} - ${delivery['type']}", - style: fontTextStyle(14, Colors.black, FontWeight.w600)), - const SizedBox(height: 8), - if (delivery.containsKey('driver')) - Row( - children: [ - const Icon(Icons.local_shipping_outlined, - color: Color(0xFF8270DB), size: 18), - const SizedBox(width: 6), - Text( - delivery['driver'], - style: fontTextStyle(13, Colors.black87, FontWeight.w500), - ) - ], - ), - const SizedBox(height: 8), - Align( - alignment: Alignment.centerRight, - child: ElevatedButton( - onPressed: () {}, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF8270DB), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), + Text( + "${widget.plan["capacity"]} - ${widget.plan["type_of_water"]}", + style: fontTextStyle(14, Colors.black, FontWeight.w600), + ), + const SizedBox(height: 10), + /* if (showButton) + Align( + alignment: Alignment.centerRight, + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF8270DB), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Text( + buttonText, + style: + fontTextStyle(13, Colors.white, FontWeight.w600), ), - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - ), - child: Text( - delivery['button'], - style: fontTextStyle(13, Colors.white, FontWeight.w600), ), - ), - ) + )*/ ], ), ); } + // ================= BUILD ================= @override Widget build(BuildContext context) { + final plan = widget.plan; + + final int totalDeliveries = deliveryDates.length; + final int pendingCount = + deliveryDates.where((d) => d.isAfter(DateTime.now())).length; + return Scaffold( backgroundColor: Colors.white, appBar: AppBar( @@ -147,18 +159,19 @@ class _PlanDetailsState extends State { icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black), onPressed: () => Navigator.pop(context), ), - title: Text("Green Valley Apartments", - style: fontTextStyle(16, Colors.black, FontWeight.w600)), + title: Text( + "Plan Details", + style: fontTextStyle(16, Colors.black, FontWeight.w600), + ), ), body: SingleChildScrollView( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Image with status + // IMAGE HEADER Stack( children: [ Image.asset( - 'images/building.png', // replace with your asset + 'images/building.png', height: 180, width: double.infinity, fit: BoxFit.cover, @@ -167,14 +180,17 @@ class _PlanDetailsState extends State { top: 16, left: 16, child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: Colors.green.withOpacity(0.9), + color: Colors.green, borderRadius: BorderRadius.circular(20), ), - child: Text("Active", - style: fontTextStyle(12, Colors.white, FontWeight.w600)), + child: Text( + "Active", + style: fontTextStyle( + 12, Colors.white, FontWeight.w600), + ), ), ), ], @@ -182,87 +198,75 @@ class _PlanDetailsState extends State { const SizedBox(height: 12), - // Apartment Info - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - children: [ - Text("Green Valley Apartments", - style: fontTextStyle(18, Colors.black, FontWeight.w600)), - const SizedBox(height: 4), - Text("Gacchibowli, Hyderabad", - style: fontTextStyle(14, Colors.black54, FontWeight.w400)), - const SizedBox(height: 4), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.water_drop, color: Color(0xFF8270DB), size: 18), - const SizedBox(width: 4), - Text("Drinking Water", - style: fontTextStyle(14, const Color(0xFF8270DB), FontWeight.w500)), - ], - ), - const SizedBox(height: 4), - Text("25 June 2025 • 24 deliveries", - style: fontTextStyle(13, Colors.black54, FontWeight.w400)), - ], - ), + // PLAN SUMMARY + Column( + children: [ + Text(plan["customerId"], + style: + fontTextStyle(18, Colors.black, FontWeight.w600)), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.water_drop, + size: 16, color: Color(0xFF8270DB)), + const SizedBox(width: 4), + Text(plan["type_of_water"], + style: fontTextStyle( + 14, const Color(0xFF8270DB), + FontWeight.w500)), + ], + ), + const SizedBox(height: 4), + Text( + "${plan["start_date"]} • $totalDeliveries deliveries", + style: fontTextStyle( + 13, Colors.black54, FontWeight.w400), + ), + ], ), const SizedBox(height: 16), - // Quantity & Balance + // INFO ROWS Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ - _buildInfoCard("10K", "Quantity"), + _buildInfoCard(plan["capacity"], "Quantity"), const SizedBox(width: 12), - _buildInfoCard("24k", "Balance", color: const Color(0xFFEFF8F1)), + _buildInfoCard( + "₹${plan["my_supplier"]["quoted_amount"]}", + "Balance", + bg: const Color(0xFFEFF8F1), + ), ], ), ), const SizedBox(height: 16), - // Schedule | Pending | Rescheduled Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ - _buildInfoCard("3/week", "Schedule"), + _buildInfoCard("${plan["weekly_count"]}/week", "Schedule"), const SizedBox(width: 12), - _buildInfoCard("14", "Pending"), + _buildInfoCard("$pendingCount", "Pending"), const SizedBox(width: 12), - _buildInfoCard("2", "Rescheduled"), + _buildInfoCard("0", "Rescheduled"), ], ), ), const SizedBox(height: 20), - // Filter chips - SingleChildScrollView( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - children: [ - _buildFilterChip("Status"), - _buildFilterChip("Date"), - _buildFilterChip("Quantity"), - _buildFilterChip("Water Type"), - ], - ), - ), - - const SizedBox(height: 16), - - // Delivery Cards - ...deliveries.map((d) => _buildDeliveryCard(d)).toList(), + // DELIVERY LIST + ...deliveryDates.map(_buildDeliveryCard).toList(), const SizedBox(height: 24), - // Bottom Buttons + // BOTTOM BUTTONS Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( @@ -271,15 +275,20 @@ class _PlanDetailsState extends State { child: OutlinedButton( onPressed: () {}, style: OutlinedButton.styleFrom( - side: const BorderSide(color: Color(0xFF8270DB)), + side: const BorderSide( + color: Color(0xFF8270DB)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), - padding: const EdgeInsets.symmetric(vertical: 14), + padding: + const EdgeInsets.symmetric(vertical: 14), + ), + child: Text( + "Edit Plan", + style: fontTextStyle(14, + const Color(0xFF8270DB), + FontWeight.w600), ), - child: Text("Edit Plan", - style: fontTextStyle( - 14, const Color(0xFF8270DB), FontWeight.w600)), ), ), const SizedBox(width: 12), @@ -291,16 +300,20 @@ class _PlanDetailsState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), - padding: const EdgeInsets.symmetric(vertical: 14), + padding: + const EdgeInsets.symmetric(vertical: 14), + ), + child: Text( + "Discontinue", + style: fontTextStyle( + 14, Colors.white, FontWeight.w600), ), - child: Text("Discontinue", - style: fontTextStyle( - 14, Colors.white, FontWeight.w600)), ), ), ], ), ), + const SizedBox(height: 24), ], ), diff --git a/lib/plans/plan_model_new.dart b/lib/plans/plan_model_new.dart new file mode 100644 index 0000000..60a8f31 --- /dev/null +++ b/lib/plans/plan_model_new.dart @@ -0,0 +1,96 @@ + + +import 'package:supplier_new/common/settings.dart'; +import 'package:geolocator/geolocator.dart'; + +class PlansModelNew { + String building_name = ''; + String address = ''; + String type_of_water = ''; + String capacity = ''; + String frequency = ''; + String quantity = ''; + String time = ''; + String averageTime = ''; + String quoted_amount = ''; + String displayAddress=''; + double lat=0; + double lng=0; + double distanceInMeters=0; + double distanceInKm=0.0; + String dbId = ''; + String status=''; + String date=''; + String imageAsset='images/building.png'; + String delivery_agent_name = ''; + String tanker_name = ''; + String water_source_location=''; + + PlansModelNew(); + + factory PlansModelNew.fromJson(Map json){ + PlansModelNew rtvm = new PlansModelNew(); + + rtvm.building_name = json['buildingName'] ?? ''; + rtvm.dbId = json['_id']?? ''; + rtvm.address = json['address'] ?? ''; + rtvm.type_of_water = json['typeofwater'] ?? ''; + rtvm.capacity = json['capacity'] ?? ''; + rtvm.quantity = json['quantity']?? ''; + rtvm.time = json['time'] ?? ''; + rtvm.date = json['dateOfOrder'] ?? ''; + rtvm.status = json['orderStatus'] ?? ''; + rtvm.quoted_amount = json['price'].toString() ?? ''; + rtvm.lng=json['longitude'] ?? 0.0; + rtvm.lat=json['latitude'] ?? 0.0; + rtvm.delivery_agent_name = json['delivery_agent'] ?? ''; + rtvm.tanker_name = json['tankerName'] ?? ''; + rtvm.water_source_location = json['water_source_location'] ?? ''; + + // Split and trim + List parts = rtvm.address.split(',').map((e) => e.trim()).toList(); + +// Usually, the locality is the part before the main city (Hyderabad)displayAddress = ""; + if (parts.length >= 2) { + rtvm.displayAddress = parts[parts.length -4]; // "Banjara Hills" + } + // Distance in meters + rtvm.distanceInMeters = double.parse( + Geolocator.distanceBetween( + rtvm.lat, + rtvm.lng, + AppSettings.supplierLatitude, + AppSettings.supplierLongitude, + ).toStringAsFixed(2), + ); + +// Distance in km + rtvm.distanceInKm = double.parse( + (rtvm.distanceInMeters / 1000).toStringAsFixed(2), + ); + + rtvm.frequency = json['frequency']?? ''; + + if(rtvm.frequency.toString().toLowerCase()=='weekly_twice'){ + rtvm.quantity="2/Week"; + } + if(rtvm.frequency.toString().toLowerCase()=='weekly_thrice'){ + rtvm.quantity="2/Week"; + } + else if(rtvm.frequency.toString().toLowerCase()=='weekly_thrice'){ + rtvm.quantity="3/Week"; + } + else if(rtvm.frequency.toString().toLowerCase()=='weekly_once'){ + rtvm.quantity="1/Week"; + } + else if(rtvm.frequency.toString().toLowerCase()=='daily'){ + rtvm.quantity="7/Week"; + } + + + return rtvm; + } + Map toJson() => { + "boreName":this.building_name, + }; +} \ No newline at end of file diff --git a/lib/plans/plan_requests_model.dart b/lib/plans/plan_requests_model.dart index 59ad216..db09da7 100644 --- a/lib/plans/plan_requests_model.dart +++ b/lib/plans/plan_requests_model.dart @@ -19,6 +19,9 @@ class PlanRequestsModel { String dbId = ''; String status=''; String frequency=''; + String endDate=''; + String startDate=''; + List dates=[]; PlanRequestsModel(); @@ -32,6 +35,7 @@ class PlanRequestsModel { rtvm.capacity = json['capacity'] ?? ''; rtvm.quantity = json['quantity']?? ''; rtvm.frequency = json['frequency']?? ''; + rtvm.dates = json['dates']?? []; if(rtvm.frequency.toString().toLowerCase()=='weekly_twice'){ rtvm.quantity="2/Week"; @@ -51,6 +55,8 @@ class PlanRequestsModel { rtvm.averageTime = json['time'] ?? ''; + rtvm.endDate = json['end_date'] ?? ''; + rtvm.startDate = json['start_date'] ?? ''; rtvm.time = json['my_supplier_entry']['time'] ?? ''; rtvm.status = json['my_supplier_entry']['status'] ?? ''; rtvm.quoted_amount = json['my_supplier_entry']['quoted_amount'].toString() ?? ''; diff --git a/lib/plans/plans_model.dart b/lib/plans/plans_model.dart index 59c36a0..1752fda 100644 --- a/lib/plans/plans_model.dart +++ b/lib/plans/plans_model.dart @@ -1,4 +1,5 @@ class PlansModel { + final String id; final String status; final String apartment; final String liters; @@ -6,8 +7,10 @@ class PlansModel { final String advance; final String deliveries; final String frequency; + final String waterType; PlansModel({ + required this.id, required this.status, required this.apartment, required this.liters, @@ -15,5 +18,22 @@ class PlansModel { required this.advance, required this.deliveries, required this.frequency, + required this.waterType, }); + + factory PlansModel.fromJson(Map json) { + final supplier = json["my_supplier"] ?? {}; + + return PlansModel( + id: json["_id"], + status: json["status"] == "processed" ? "Active" : "Inactive", + apartment: json["customerId"], // replace when buildingName available + liters: "${json["capacity"]} - ${json["type_of_water"]}", + price: "₹${supplier["quoted_amount"] ?? "--"}", + advance: "--", + deliveries: "${json["dates"]?.length ?? 0} Deliveries", + frequency: "${json["weekly_count"]}/week", + waterType: json["type_of_water"], + ); + } } \ No newline at end of file diff --git a/lib/resources/resources_drivers.dart b/lib/resources/resources_drivers.dart index 24d942f..aa47eab 100644 --- a/lib/resources/resources_drivers.dart +++ b/lib/resources/resources_drivers.dart @@ -61,6 +61,10 @@ class _ResourcesDriverScreenState extends State { final _commissionCtrl = TextEditingController(); final _joinDateCtrl = TextEditingController(); + int onDeliveryCount = 0; + int availableCount = 0; + int offlineCount = 0; + // Dropdown state String? _status; // 'available' | 'on delivery' | 'offline' final List _statusOptions = const [ @@ -128,9 +132,31 @@ class _ResourcesDriverScreenState extends State { final data = (jsonDecode(response)['data'] as List) .map((e) => DriversModel.fromJson(e)) .toList(); + + int onDelivery = 0; + int available = 0; + int offline = 0; + + for (final d in data) { + switch (d.status) { + case 'on delivery': + onDelivery++; + break; + case 'available': + available++; + break; + case 'offline': + offline++; + break; + } + } + if (!mounted) return; setState(() { driversList = data; + onDeliveryCount = onDelivery; + availableCount = available; + offlineCount = offline; isLoading = false; }); } catch (e) { @@ -504,16 +530,30 @@ class _ResourcesDriverScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 16), child: IntrinsicHeight( child: Row( - children: const [ + children: [ Expanded( - child: SmallMetricBox(title: 'On delivery', value: '2')), - SizedBox(width: 8), + child: SmallMetricBox( + title: 'On delivery', + value: onDeliveryCount.toString(), + ), + ), + const SizedBox(width: 8), Expanded( - child: SmallMetricBox(title: 'Available', value: '3')), - SizedBox(width: 8), - Expanded(child: SmallMetricBox(title: 'Offline', value: '1')), + child: SmallMetricBox( + title: 'Available', + value: availableCount.toString(), + ), + ), + const SizedBox(width: 8), + Expanded( + child: SmallMetricBox( + title: 'Offline', + value: offlineCount.toString(), + ), + ), ], ), + ), ), diff --git a/lib/resources/resources_fleet.dart b/lib/resources/resources_fleet.dart index 4cafc6a..a18dc85 100644 --- a/lib/resources/resources_fleet.dart +++ b/lib/resources/resources_fleet.dart @@ -75,6 +75,9 @@ class _ResourcesFleetScreenState extends State { String search = ''; bool isLoading = false; List tankersList = []; + int activeCount = 0; + int inactiveCount = 0; + int maintenanceCount = 0; @override void initState() { @@ -99,9 +102,29 @@ class _ResourcesFleetScreenState extends State { final data = (jsonDecode(response)['data'] as List) .map((e) => TankersModel.fromJson(e)) .toList(); + + int active = 0; + int inactive = 0; + int maintenance = 0; + + for (final t in data) { + final statuses = List.from(t.availability); + + if (statuses.contains('undermaintanence')) { + maintenance++; + } else if (statuses.contains('available') || statuses.contains('in-use')|| statuses.contains('empty')) { + active++; + } else if (statuses.contains('inactive')) { + inactive++; + } + } + if (!mounted) return; setState(() { tankersList = data; + activeCount = active; + inactiveCount = inactive; + maintenanceCount = maintenance; isLoading = false; }); } catch (e) { @@ -475,12 +498,12 @@ class _ResourcesFleetScreenState extends State { IntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, - children: const [ - Expanded(child: SmallMetricBox(title: 'Active', value: '2')), - SizedBox(width: 8), - Expanded(child: SmallMetricBox(title: 'Inactive', value: '3')), - SizedBox(width: 8), - Expanded(child: SmallMetricBox(title: 'Under Maintenance', value: '1')), + children: [ + Expanded(child: SmallMetricBox(title: 'Active', value: activeCount.toString())), + const SizedBox(width: 8), + Expanded(child: SmallMetricBox(title: 'Inactive', value: inactiveCount.toString())), + const SizedBox(width: 8), + Expanded(child: SmallMetricBox(title: 'Under Maintenance', value: maintenanceCount.toString())), ], ), ), diff --git a/lib/resources/resources_main.dart b/lib/resources/resources_main.dart index e85c537..3a187fd 100644 --- a/lib/resources/resources_main.dart +++ b/lib/resources/resources_main.dart @@ -19,7 +19,12 @@ class _ResourcesMainScreenState extends State @override void initState() { super.initState(); - _tabController = TabController(length: _labels.length, vsync: this); + _tabController = TabController( + length: _labels.length, + vsync: this, + initialIndex: AppSettings.resourcesInitialTab, + ); + AppSettings.resourcesInitialTab = 0; } @override diff --git a/lib/resources/resources_sources.dart b/lib/resources/resources_sources.dart index 6709001..f2db77f 100644 --- a/lib/resources/resources_sources.dart +++ b/lib/resources/resources_sources.dart @@ -102,6 +102,9 @@ class _ResourcesSourceScreenState extends State { double? lat; double? lng; String? address; + int drinkingCount = 0; + int boreCount = 0; + int bothCount = 0; String? _required(String? v, {String field = "This field"}) { if (v == null || v.trim().isEmpty) return "$field is required"; @@ -143,9 +146,31 @@ class _ResourcesSourceScreenState extends State { final data = (jsonDecode(response)['data'] as List) .map((e) => SourceLocationsModel.fromJson(e)) .toList(); + + int drinking = 0; + int bore = 0; + int both = 0; + + for (final s in data) { + switch ((s.water_type ?? '').toLowerCase()) { + case 'drinking water': + drinking++; + break; + case 'bore water': + bore++; + break; + case 'both': + both++; + break; + } + } + if (!mounted) return; setState(() { sourceLocationsList = data; + drinkingCount = drinking; + boreCount = bore; + bothCount = both; isLoading = false; }); } catch (e) { @@ -536,14 +561,30 @@ class _ResourcesSourceScreenState extends State { IntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, - children: const [ - Expanded(child: SmallMetricBox(title: 'Drinking water', value: '2')), - SizedBox(width: 8), - Expanded(child: SmallMetricBox(title: 'Bore water', value: '3')), - SizedBox(width: 8), - Expanded(child: SmallMetricBox(title: 'Both', value: '1')), + children: [ + Expanded( + child: SmallMetricBox( + title: 'Drinking water', + value: drinkingCount.toString(), + ), + ), + const SizedBox(width: 8), + Expanded( + child: SmallMetricBox( + title: 'Bore water', + value: boreCount.toString(), + ), + ), + const SizedBox(width: 8), + Expanded( + child: SmallMetricBox( + title: 'Both', + value: bothCount.toString(), + ), + ), ], ), + ), ], ), @@ -596,6 +637,20 @@ class _ResourcesSourceScreenState extends State { Expanded( child: isLoading ? const Center(child: CircularProgressIndicator()) + : (filtered.isEmpty + ? Center( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12), + child: Text( + 'No Data Available', + style: fontTextStyle( + 12, + const Color(0xFF939495), + FontWeight.w500), + ), + ), + ) : ListView.separated( itemCount: filtered.length, separatorBuilder: (_, __) => const SizedBox(height: 10), @@ -622,7 +677,7 @@ class _ResourcesSourceScreenState extends State { ), ); }, - ), + )), ), ], ), diff --git a/pubspec.lock b/pubspec.lock index f390594..32b9d8e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -944,6 +944,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + simple_gesture_detector: + dependency: transitive + description: + name: simple_gesture_detector + sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3 + url: "https://pub.dev" + source: hosted + version: "0.2.1" sky_engine: dependency: transitive description: flutter @@ -989,6 +997,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + table_calendar: + dependency: "direct main" + description: + name: table_calendar + sha256: "7f1270313c0cdb245b583ed8518982c01d4a7e95869b3c30abcbae3b642c45d0" + url: "https://pub.dev" + source: hosted + version: "3.0.8" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ae70739..b770bb4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ dependencies: provider: ^6.0.5 google_maps_place_picker_mb: ^3.0.2 dropdown_button2: ^2.0.0 + table_calendar: ^3.0.2 dev_dependencies: flutter_test: