master
Sneha 3 months ago
parent aa1d86d17e
commit a7a868fcdd

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

@ -1,202 +1,526 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/common/settings.dart';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';
import '../common/settings.dart';
class DeliveryCalendarScreen extends StatefulWidget { class SupplyCalendarScreen extends StatefulWidget {
const DeliveryCalendarScreen({super.key});
@override @override
State<DeliveryCalendarScreen> createState() => _DeliveryCalendarScreenState(); State<SupplyCalendarScreen> createState() => _SupplyCalendarScreenState();
} }
class _DeliveryCalendarScreenState extends State<DeliveryCalendarScreen> { class _SupplyCalendarScreenState extends State<SupplyCalendarScreen> {
DateTime _focusedMonth = DateTime(2025, 10); late DateTime _focusedDay;
late List<Map<String, dynamic>> _calendarData; late DateTime _firstDay;
late DateTime _lastDay;
Map<DateTime, List<Map<String, dynamic>>> calendarEvents = {};
DateTime? _selectedDay;
bool isLoading = true;
@override @override
void initState() { void initState() {
super.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<void> fetchOrdersFromApi() async {
try {
final response = await AppSettings.getAcceptedOrdersFromUsers();
final decoded = jsonDecode(response);
if (decoded == null || decoded['data'] == null) {
setState(() => isLoading = false);
return;
}
final List<dynamic> orders = decoded['data'];
calendarEvents.clear();
for (var order in orders) {
String? originalDate = order["date"];
String? newDate = order["reScheduleDateOfDelivery"];
String? resStatus = order["rescheduleOrderStatus"];
bool isRescheduled =
newDate != null && newDate.isNotEmpty && resStatus != null && resStatus.isNotEmpty;
// ===================== ORIGINAL DATE EVENT =====================
if (originalDate != null && order["orderStatus"] != "cancelled") {
try {
DateTime d = DateTime.parse(originalDate);
DateTime key = DateTime(d.year, d.month, d.day);
calendarEvents.putIfAbsent(key, () => []);
calendarEvents[key]!.add({
"status": isRescheduled ? "rescheduled_from" : "delivery",
"_id": order["_id"],
"supplierName": order["supplierName"] ?? "Supplier",
"capacity": order["capacity"] ?? "",
"time": order["time"] ?? "",
"originalDate": originalDate,
"newDate": newDate,
});
} catch (e) {}
}
// ===================== NEW RESCHEDULED DATE EVENT =====================
if (isRescheduled) {
try {
DateTime d2 = DateTime.parse(newDate);
DateTime key2 = DateTime(d2.year, d2.month, d2.day);
calendarEvents.putIfAbsent(key2, () => []);
calendarEvents[key2]!.add({
"status": "rescheduled_to",
"_id": order["_id"],
"supplierName": order["supplierName"] ?? "Supplier",
"capacity": order["capacity"] ?? "",
"time": order["time"] ?? "",
"originalDate": originalDate,
"newDate": newDate,
});
} catch (e) {}
}
// ===================== CANCELLED EVENT =====================
if (order["orderStatus"] == "cancelled") {
try {
DateTime d = DateTime.parse(originalDate!);
DateTime key = DateTime(d.year, d.month, d.day);
calendarEvents.putIfAbsent(key, () => []);
calendarEvents[key]!.add({
"status": "cancelled",
"_id": order["_id"],
"supplierName": order["supplierName"] ?? "Supplier",
"capacity": order["capacity"] ?? "",
"time": order["time"] ?? "",
});
} catch (e) {}
}
}
setState(() => isLoading = false);
} catch (e) {
setState(() => isLoading = false);
}
}
// ===========================================================================
// CANCEL ORDER API
// ===========================================================================
Future<void> cancelDelivery(String id) async {
/* try {
await AppSettings.cancelPlanOrder(id); // your endpoint
await fetchOrdersFromApi();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Order cancelled successfully")));
} catch (e) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Cancel failed")));
}*/
} }
List<Map<String, dynamic>> _generateCalendarData() { void _confirmCancel(String orderId) {
return [ showDialog(
{"day": 1, "status": "Delivered"}, context: context,
{"day": 2, "status": "Delivered"}, builder: (_) => AlertDialog(
{"day": 3, "status": "Rescheduled"}, title: Text("Cancel Delivery"),
{"day": 4, "status": "Delivered"}, content: Text("Are you sure you want to cancel this delivery?"),
{"day": 5, "status": "Delivered"}, actions: [
{"day": 6, "status": "Delivered"}, TextButton(
{"day": 7, "status": "Cancelled"}, onPressed: () => Navigator.pop(context), child: Text("No")),
{"day": 8, "status": "Delivered"}, TextButton(
{"day": 9, "status": "Delivered"}, onPressed: () {
{"day": 10, "status": "Delivered"}, Navigator.pop(context);
{"day": 11, "status": "Cancelled"}, cancelDelivery(orderId);
{"day": 12, "status": "Delivery"}, },
{"day": 13, "status": "Delivery"}, child: Text("Yes, Cancel", style: TextStyle(color: Colors.red))),
{"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"},
];
} }
Color _getBackgroundColor(String status) { // ===========================================================================
switch (status) { // HELPER FUNCTIONS
case "Delivered": // ===========================================================================
return const Color(0xFFE6F4EA); String formatShort(String date) {
case "Cancelled": try {
return const Color(0xFFFDE8E8); final d = DateTime.parse(date);
case "Rescheduled": return "${d.day} ${monthShort[d.month - 1]}";
return const Color(0xFFF2F2F2); } catch (e) {
case "Delivery": return date;
return const Color(0xFFEFF4FF);
default:
return Colors.white;
} }
} }
Color _getTextColor(String status) { List<String> monthShort = [
switch (status) { "Jan","Feb","Mar","Apr","May","Jun",
case "Delivered": "Jul","Aug","Sep","Oct","Nov","Dec"
return Colors.green; ];
case "Cancelled":
return Colors.red; Widget _buildStatusIcon(String status) {
case "Rescheduled": if (status == "rescheduled_from") {
return Colors.black54; return Icon(Icons.subdirectory_arrow_left, color: Colors.orange);
case "Delivery": }
return const Color(0xFF3B6FE0); if (status == "rescheduled_to") {
default: return Icon(Icons.subdirectory_arrow_right, color: Colors.deepOrange);
return Colors.black87; }
if (status == "cancelled") {
return Icon(Icons.cancel, color: Colors.red);
} }
return Icon(Icons.local_shipping, color: Colors.blue);
} }
Widget _getStatusIcon(String status) { String _buildStatusText(Map<String, dynamic> d) {
switch (status) { String status = d["status"];
case "Delivered": String? originalDate = d["originalDate"];
return const Icon(Icons.check, size: 16, color: Colors.green); String? newDate = d["newDate"];
case "Cancelled":
return const Icon(Icons.close, size: 16, color: Colors.red); if (status == "rescheduled_from") {
case "Rescheduled": return "Rescheduled from this date → ${formatShort(newDate!)}";
return const Icon(Icons.access_time, size: 16, color: Colors.black54); }
case "Delivery": if (status == "rescheduled_to") {
return const Icon(Icons.local_shipping, size: 16, color: Color(0xFF3B6FE0)); return "Rescheduled to this date (from ${formatShort(originalDate!)})";
default: }
return const SizedBox.shrink(); if (status == "cancelled") {
return "Cancelled";
} }
return status;
} }
String _getMonthYear() { // ===========================================================================
return DateFormat('MMM yyyy').format(_focusedMonth).toUpperCase(); // OPEN RESCHEDULE CALENDAR
// ===========================================================================
Future<void> openRescheduleCalendar(Map<String, dynamic> delivery) async {
DateTime now = DateTime.now();
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: now.add(Duration(days: 1)),
firstDate: now.add(Duration(days: 1)),
lastDate: DateTime(now.year + 1, 12, 31),
);
if (pickedDate == null) return;
String formatted =
"${pickedDate.year}-${pickedDate.month.toString().padLeft(2,'0')}-${pickedDate.day.toString().padLeft(2,'0')}";
await sendRescheduleToServer(delivery["_id"], formatted);
}
Future<void> sendRescheduleToServer(String id, String newDate) async {
/*try {
final payload = {"reScheduleDateOfDelivery": newDate};
await AppSettings.rescheduleOrder(id, payload);
await fetchOrdersFromApi();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Delivery rescheduled to $newDate")));
} catch (e) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Reschedule failed")));
}*/
}
// ===========================================================================
// SHOW DELIVERY LIST
// ===========================================================================
void showDeliveryList(DateTime date) {
final key = DateTime(date.year, date.month, date.day);
final events = calendarEvents[key];
if (events == null || events.isEmpty) return;
bool isPast = date.isBefore(DateTime(
DateTime.now().year, DateTime.now().month, DateTime.now().day));
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) {
final list = events.toList();
return DraggableScrollableSheet(
initialChildSize: 0.9,
maxChildSize: 0.9,
minChildSize: 0.4,
builder: (_, controller) {
return Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
children: [
Row(
children: [
Expanded(
child: Text(
"Select Delivery",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Icon(Icons.close)),
],
),
Expanded(
child: ListView(
controller: controller,
children: list.map((delivery) {
return ListTile(
leading: _buildStatusIcon(delivery["status"]),
title: Text(_buildStatusText(delivery)),
subtitle: Text(
"Capacity: ${delivery["capacity"]}${delivery["time"]}"),
onTap: () {
Navigator.pop(context);
showActionsForSingleDelivery(delivery, isPast);
},
);
}).toList(),
),
)
],
),
);
},
);
},
);
} }
int _daysInMonth(DateTime date) { // ===========================================================================
final firstDayThisMonth = DateTime(date.year, date.month, 1); // ACTIONS SHEET
final firstDayNextMonth = DateTime(date.year, date.month + 1, 1); // ===========================================================================
return firstDayNextMonth.difference(firstDayThisMonth).inDays; void showActionsForSingleDelivery(Map<String, dynamic> delivery, bool isPast) {
String status = delivery["status"];
// ORIGINAL DATE NO ACTIONS
if (status == "rescheduled_from") {
showModalBottomSheet(
context: context,
builder: (_) {
return Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Rescheduled from this date",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
Text("This delivery was moved to another date.",
style: TextStyle(color: Colors.grey)),
],
),
);
},
);
return;
}
// CANCELLED ORDER NO ACTIONS
if (status == "cancelled") {
showModalBottomSheet(
context: context,
builder: (_) =>
Container(
padding: EdgeInsets.all(20),
child: Text("Order was cancelled", style: TextStyle(color: Colors.red)),
),
);
return;
}
// NORMAL OR RESCHEDULED_TO (ACTIONS ENABLED)
showModalBottomSheet(
context: context,
builder: (_) {
return Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("${delivery["supplierName"]}",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
if (!isPast) ...[
ListTile(
leading: Icon(Icons.calendar_month, color: Colors.blue),
title: Text("Reschedule Delivery"),
onTap: () {
Navigator.pop(context);
openRescheduleCalendar(delivery);
},
),
ListTile(
leading: Icon(Icons.delete_forever, color: Colors.red),
title: Text("Cancel Delivery"),
onTap: () {
Navigator.pop(context);
_confirmCancel(delivery["_id"]);
},
),
],
if (isPast)
Text("Past delivery — actions disabled",
style: TextStyle(color: Colors.grey)),
],
),
);
},
);
} }
// ===========================================================================
// BUILD UI
// ===========================================================================
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final int totalDays = _daysInMonth(_focusedMonth); if (isLoading) {
final int firstWeekday = DateTime(_focusedMonth.year, _focusedMonth.month, 1).weekday; return Scaffold(body: Center(child: CircularProgressIndicator()));
final int totalSlots = totalDays + (firstWeekday - 1); }
final height = MediaQuery.of(context).size.height;
return Scaffold( return Scaffold(
backgroundColor: Colors.white, 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( body: Column(
children: [ children: [
const SizedBox(height: 8), SizedBox(height: 50),
Text(_getMonthYear(), style: fontTextStyle(16, Colors.black, FontWeight.w600)),
const SizedBox(height: 8), Text(
"${monthShort[_focusedDay.month - 1]} ${_focusedDay.year}",
// Weekdays Row style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
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"))),
],
),
), ),
const SizedBox(height: 8),
// Calendar Grid
Expanded( Expanded(
child: GridView.builder( child: TableCalendar(
padding: const EdgeInsets.symmetric(horizontal: 4), firstDay: _firstDay,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( lastDay: _lastDay,
crossAxisCount: 7, focusedDay: _focusedDay,
crossAxisSpacing: 4, headerVisible: false,
mainAxisSpacing: 4, calendarFormat: CalendarFormat.month,
), rowHeight: (height - 200) / 6,
itemCount: totalSlots, daysOfWeekHeight: 40,
itemBuilder: (context, index) {
if (index < firstWeekday - 1) { onPageChanged: (focused) {
return const SizedBox.shrink(); setState(() => _focusedDay = focused);
}
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),
],
),
);
}, },
onDaySelected: (selected, focused) {
_focusedDay = focused;
_selectedDay = selected;
showDeliveryList(selected);
setState(() {});
},
calendarBuilders: CalendarBuilders(
defaultBuilder: (context, date, _) {
final key = DateTime(date.year, date.month, date.day);
final events = calendarEvents[key];
Color bg = Colors.transparent;
if (events != null && events.isNotEmpty) {
String s = events.first["status"];
if (s == "rescheduled_to")
bg = Colors.deepOrange.withOpacity(0.10);
else if (s == "rescheduled_from")
bg = Colors.orange.withOpacity(0.10);
else if (s == "cancelled")
bg = Colors.red.withOpacity(0.10);
else
bg = Colors.blue.withOpacity(0.08);
}
// Grouping counts (delivery, cancelled, rescheduled)
Map<String, int> grouped = {};
if (events != null) {
for (var e in events) {
grouped[e["status"]] = (grouped[e["status"]] ?? 0) + 1;
}
}
return Container(
decoration: BoxDecoration(
color: bg,
border: Border.all(color: Colors.grey.shade300)),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("${date.day}",
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.bold)),
if (events != null && events.isNotEmpty)
Wrap(
spacing: 3,
alignment: WrapAlignment.center,
children: grouped.entries.map((entry) {
IconData icon;
Color color;
if (entry.key == "rescheduled_from") {
icon = Icons.subdirectory_arrow_left;
color = Colors.orange;
}
else if (entry.key == "rescheduled_to") {
icon = Icons.subdirectory_arrow_right;
color = Colors.deepOrange;
}
else if (entry.key == "cancelled") {
icon = Icons.cancel;
color = Colors.red;
}
else {
icon = Icons.local_shipping;
color = Colors.blue;
}
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 10, color: color),
Text("x${entry.value}",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: color)),
],
);
}).toList(),
)
],
),
),
);
},
),
), ),
), ),
], ],

@ -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/order_requests.dart';
import 'package:supplier_new/orders/search_order_appbar.dart'; import 'package:supplier_new/orders/search_order_appbar.dart';
import 'package:supplier_new/plans/all_plans.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/plan_requests.dart';
import 'package:supplier_new/plans/search_plan_appbar.dart'; import 'package:supplier_new/plans/search_plan_appbar.dart';
import 'package:supplier_new/set_rates/set_rates.dart'; import 'package:supplier_new/set_rates/set_rates.dart';
import '../login/login.dart'; import '../login/login.dart';
import '../profile/fleet.dart'; import '../profile/fleet.dart';
import '../resources/drivers_model.dart';
import '../resources/resources_fleet.dart'; import '../resources/resources_fleet.dart';
import '../resources/resources_main.dart'; import '../resources/resources_main.dart';
import '../resources/tankers_model.dart';
import 'calander.dart'; import 'calander.dart';
class DashboardScreen extends StatefulWidget { class DashboardScreen extends StatefulWidget {
@ -323,7 +326,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DeliveryCalendarScreen(), builder: (context) => SupplierCalendar(),
), ),
); );
}, },
@ -763,6 +766,82 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> { class _HomeScreenState extends State<HomeScreen> {
@override
void initState() {
super.initState();
_fetchDriverCounts();
_fetchTankerCounts();
}
Future<void> _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<void> _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<String>.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SingleChildScrollView( return SingleChildScrollView(
@ -851,18 +930,61 @@ class _HomeScreenState extends State<HomeScreen> {
crossAxisSpacing: 12, crossAxisSpacing: 12,
mainAxisSpacing: 12, mainAxisSpacing: 12,
childAspectRatio: 1.5, childAspectRatio: 1.5,
children: const [ children: [
DashboardCard( GestureDetector(
title: "Active Vehicles", onTap: () async {
value: "12/14", // 👉 Switch to Resources tab (index = 3)
subtitle: "2 in maintenance", final dashboardState =
icon: Icons.local_shipping_outlined, 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( GestureDetector(
title: "Available Drivers", onTap: () async {
value: "2/10", // 👉 Tell Resources to open Drivers tab
subtitle: "6 on delivery, 2 offline", AppSettings.resourcesInitialTab = 1; // DRIVERS
icon: Icons.person_outline,
// 👉 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,
),
), ),
], ],
), ),

@ -158,6 +158,7 @@ class AppSettings{
static String getAcceptedOrdersFromUsersUrl = host + 'getAllTankersBookingdetails'; static String getAcceptedOrdersFromUsersUrl = host + 'getAllTankersBookingdetails';
static String getPlanRequestsFromUsersUrl = host + 'getuserRequestbookingsforplansforsupplier'; static String getPlanRequestsFromUsersUrl = host + 'getuserRequestbookingsforplansforsupplier';
static String acceptPlanRequestsUrl = host + 'supplier/recurring/respond'; static String acceptPlanRequestsUrl = host + 'supplier/recurring/respond';
static String acceptPlanRequestsUrl1 = host + 'recurringRequestedBooking';
static String getTankersUrl = host + 'getTankers'; static String getTankersUrl = host + 'getTankers';
static String getTankerDetailsByNameUrl = host + 'getsingledetails'; static String getTankerDetailsByNameUrl = host + 'getsingledetails';
static String addTankerUrl = host + 'addTankers'; static String addTankerUrl = host + 'addTankers';
@ -179,10 +180,30 @@ class AppSettings{
static String updatePumpFeeUrl = host + 'suppliers'; static String updatePumpFeeUrl = host + 'suppliers';
static String assignTankerUrl = host + 'assign-deliveryboy-tanker'; static String assignTankerUrl = host + 'assign-deliveryboy-tanker';
static String getDriverDetailsByPhoneUrl = host + 'getsingledeliveryboydetails'; 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<String> existingCreditCustomerIds = [];
static String formDouble(dynamic s) { static String formDouble(dynamic s) {
var comma = NumberFormat('#,##,###.##', 'en_IN'); var comma = NumberFormat('#,##,###.##', 'en_IN');
@ -504,15 +525,15 @@ class AppSettings{
static Future<bool> acceptPlanRequests(payload,dbId) async { static Future<bool> 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) { if (response.statusCode == 200) {
return true; return true;
} else if (response.statusCode == 401) { } else if (response.statusCode == 401) {
bool status = await AppSettings.resetToken(); bool status = await AppSettings.resetToken();
if (status) { 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) { if (response.statusCode == 200) {
return true; return true;
} else { } else {
@ -1175,6 +1196,234 @@ class AppSettings{
} }
} }
static Future<String> 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<String> 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<String> 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<bool> createAdvanceRequest(Map<String, dynamic> 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<bool> acceptAdvanceFromCustomer(Map<String, dynamic> 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<bool> 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<String> 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<Map<String, dynamic>> 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<String> 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*/ /*Apis ends here*/

@ -1,92 +1,122 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/financials/add_transaction_for_credit_account.dart'; import 'package:supplier_new/financials/add_transaction_for_credit_account.dart';
class BuildingTransactionsDetails extends StatefulWidget { 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 @override
State<BuildingTransactionsDetails> createState() => _BuildingTransactionsDetailsState(); State<BuildingTransactionsDetails> createState() =>
_BuildingTransactionsDetailsState();
} }
class _BuildingTransactionsDetailsState extends State<BuildingTransactionsDetails> { class _BuildingTransactionsDetailsState
extends State<BuildingTransactionsDetails> {
bool isLoading = true;
List<Map<String, dynamic>> transactions = [];
double advanceBalance = 0;
double receivableBalance = 0;
@override
void initState() {
super.initState();
fetchTransactions();
}
// ========================= API =========================
Future<void> 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<Map<String, dynamic>>((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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.white,
appBar: AppSettings.supplierAppBarWithActionsText(widget.customerId, context),
floatingActionButton: SizedBox( floatingActionButton: SizedBox(
width: 52, // default is 56 width: 52,
height: 52, // make it bigger height: 52,
child: FloatingActionButton( child: FloatingActionButton(
shape: const CircleBorder(), // ensures perfect round shape shape: const CircleBorder(),
backgroundColor: Colors.black, backgroundColor: Colors.black,
onPressed: (){ onPressed: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => AddCreditTransactionPage(), builder: (_) => AddCreditTransactionPage(),
), ),
); );
}, },
child:Image.asset( child: Image.asset(
"images/plus.png", // your custom image "images/plus.png",
width: 20, width: 20,
height: 20, height: 20,
color: Colors.white, // optional: apply tint color: Colors.white,
), ),
), ),
), ),
body: SafeArea( body: SafeArea(
child: Column( child: Column(
children: [ 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(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
OutlinedButton.icon( OutlinedButton.icon(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: () {}, onPressed: () {},
icon: const Icon(Icons.download), icon: const Icon(Icons.download),
label: const Text("Statement"), label: const Text("Statement"),
@ -95,7 +125,7 @@ class _BuildingTransactionsDetailsState extends State<BuildingTransactionsDetail
), ),
), ),
// Orders and balances // ================= BALANCES =================
Padding( Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Container( child: Container(
@ -106,92 +136,21 @@ class _BuildingTransactionsDetailsState extends State<BuildingTransactionsDetail
), ),
child: Column( child: Column(
children: [ children: [
// Total orders
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Total Orders",
style: TextStyle(fontSize: 16),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: const [
Text(
"45",
style: TextStyle(
fontSize: 22, fontWeight: FontWeight.bold),
),
Text(
"12 complete",
style:
TextStyle(fontSize: 13, color: Colors.grey),
)
],
)
],
),
const SizedBox(height: 16),
// Balances
Row( Row(
children: [ children: [
Expanded( Expanded(
child: Container( child: _balanceBox(
padding: const EdgeInsets.all(12), title: "Receivable Balance",
decoration: BoxDecoration( amount: receivableBalance,
color: Colors.white, color: Colors.red,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text("Receivable Balance"),
SizedBox(height: 4),
Text(
"₹24,000",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.red),
),
SizedBox(height: 2),
Text(
"40.61% of total credit",
style: TextStyle(
fontSize: 12, color: Colors.grey),
)
],
),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Container( child: _balanceBox(
padding: const EdgeInsets.all(12), title: "Advance Balance",
decoration: BoxDecoration( amount: advanceBalance,
color: Colors.white, color: Colors.green,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text("Advance Balance"),
SizedBox(height: 4),
Text(
"₹24,000",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.green),
),
SizedBox(height: 2),
Text(
"60.41% of total credit",
style: TextStyle(
fontSize: 12, color: Colors.grey),
)
],
),
), ),
), ),
], ],
@ -201,7 +160,7 @@ class _BuildingTransactionsDetailsState extends State<BuildingTransactionsDetail
), ),
), ),
// Buttons // ================= ACTION BUTTONS =================
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: Row(
@ -209,13 +168,6 @@ class _BuildingTransactionsDetailsState extends State<BuildingTransactionsDetail
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
onPressed: () {}, onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text("Add Transaction"), child: const Text("Add Transaction"),
), ),
), ),
@ -223,12 +175,6 @@ class _BuildingTransactionsDetailsState extends State<BuildingTransactionsDetail
Expanded( Expanded(
child: OutlinedButton( child: OutlinedButton(
onPressed: () {}, onPressed: () {},
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text("Request Top up"), child: const Text("Request Top up"),
), ),
), ),
@ -238,34 +184,36 @@ class _BuildingTransactionsDetailsState extends State<BuildingTransactionsDetail
const SizedBox(height: 12), const SizedBox(height: 12),
// History label // ================= HISTORY =================
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 16), padding: EdgeInsets.symmetric(horizontal: 16),
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
"HISTORY", "HISTORY",
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15), style: TextStyle(fontWeight: FontWeight.w600),
), ),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// History list
Expanded( Expanded(
child: ListView( child: isLoading
padding: const EdgeInsets.symmetric(horizontal: 16), ? const Center(child: CircularProgressIndicator())
children: [ : ListView.builder(
_historyItem( padding:
"Transaction Description", "21 August", "+ ₹2,580", true), const EdgeInsets.symmetric(horizontal: 16),
_historyItem( itemCount: transactions.length,
"Transaction Description", "19 August", "- ₹748", false), itemBuilder: (context, index) {
_historyItem( final txn = transactions[index];
"Transaction Description", "16 August", "- ₹10,000", false), return _historyItem(
_historyItem( txn["title"],
"Transaction Description", "12 August", "- ₹500", false), txn["date"],
], "${txn["amount"].abs()}",
txn["isCredit"],
);
},
), ),
), ),
], ],
@ -274,6 +222,36 @@ class _BuildingTransactionsDetailsState extends State<BuildingTransactionsDetail
); );
} }
// ================= HELPERS =================
Widget _balanceBox({
required String title,
required double amount,
required Color color,
}) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title),
const SizedBox(height: 4),
Text(
"${amount.toStringAsFixed(0)}",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
);
}
Widget _historyItem( Widget _historyItem(
String title, String date, String amount, bool isCredit) { String title, String date, String amount, bool isCredit) {
return ListTile( return ListTile(

@ -1,47 +1,112 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:supplier_new/common/settings.dart';
class CreateCreditAccountScreen extends StatefulWidget { class CreateCreditAccountScreen extends StatefulWidget {
@override @override
_CreateCreditAccountScreenState createState() => _CreateCreditAccountScreenState(); _CreateCreditAccountScreenState createState() =>
_CreateCreditAccountScreenState();
} }
class _CreateCreditAccountScreenState extends State<CreateCreditAccountScreen> {
String? selectedCustomer; class _CreateCreditAccountScreenState
String paymentTerm = 'Net 30'; extends State<CreateCreditAccountScreen> {
final creditLimitController = TextEditingController(text: '₹500'); String? selectedCustomerId;
final openingBalanceController = TextEditingController(text: '₹500');
final List<Map<String, String>> customers = [ final creditLimitController = TextEditingController();
{'name': 'Ramakrishna', 'date': '20 August'}, final openingBalanceController = TextEditingController();
{'name': 'Mallesham Water Supplies', 'date': '21 August'},
{'name': 'My Home Bhooja', 'date': '21 August'}, bool isLoading = true;
];
final List<String> paymentTerms = ['Net 15', 'Net 30', 'Net 45']; List<Map<String, dynamic>> customers = [];
@override
void initState() {
super.initState();
_fetchCustomers();
}
Future<void> _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<String> seenCustomerIds = {};
final List<Map<String, dynamic>> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Create Credit Account'), title: const Text('Create Credit Account'),
actions: [
TextButton(
onPressed: () {},
child: Text('HELP', style: TextStyle(color: Colors.blue)),
)
],
backgroundColor: Colors.white, backgroundColor: Colors.white,
foregroundColor: Colors.black, foregroundColor: Colors.black,
elevation: 1, elevation: 1,
), ),
body: Padding( body: isLoading
? const Center(child: CircularProgressIndicator())
: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: ListView( child: ListView(
children: [ children: [
Text('SELECT CUSTOMER', style: TextStyle(fontWeight: FontWeight.bold)), /// SELECT CUSTOMER
const SizedBox(height: 10), 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) { ...customers.map((customer) {
final isSelected =
selectedCustomerId == customer["customerId"];
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
selectedCustomer = customer['name']; selectedCustomerId = customer["customerId"];
}); });
}, },
child: Container( child: Container(
@ -49,21 +114,30 @@ class _CreateCreditAccountScreenState extends State<CreateCreditAccountScreen> {
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: selectedCustomer == customer['name'] ? Colors.black : Colors.grey.shade300, color:
isSelected ? Colors.black : Colors.grey.shade300,
width: 1.5, width: 1.5,
), ),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
color: Colors.white,
), ),
child: Row( child: Row(
children: [ children: [
CircleAvatar(child: Icon(Icons.person)), const CircleAvatar(
child: Icon(Icons.person),
),
const SizedBox(width: 10), const SizedBox(width: 10),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(customer['name']!, style: TextStyle(fontSize: 16)), Text(
Text(customer['date']!, style: TextStyle(color: Colors.grey)), customer["name"],
style: const TextStyle(fontSize: 16),
),
Text(
customer["date"],
style:
const TextStyle(color: Colors.grey),
),
], ],
) )
], ],
@ -71,73 +145,91 @@ class _CreateCreditAccountScreenState extends State<CreateCreditAccountScreen> {
), ),
); );
}), }),
const SizedBox(height: 20), 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), const SizedBox(height: 10),
TextField( TextField(
controller: creditLimitController, controller: creditLimitController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: InputDecoration( decoration: const InputDecoration(
labelText: 'Credit Limit (in ₹) *', labelText: 'Credit Limit (₹) *',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 15),
DropdownButtonFormField2<String>(
decoration: InputDecoration(
labelText: 'Payment Terms *',
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
value: paymentTerm,
items: paymentTerms.map((term) {
return DropdownMenuItem<String>(
value: term,
child: Text(term),
);
}).toList(),
onChanged: (value) {
setState(() {
paymentTerm = value!;
});
},
), ),
const SizedBox(height: 15), const SizedBox(height: 15),
TextField( TextField(
controller: openingBalanceController, controller: openingBalanceController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: InputDecoration( decoration: const InputDecoration(
labelText: 'Opening Balance (in ₹) *', labelText: 'Opening Balance (₹) *',
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Row( Row(
children: [ children: const [
Icon(Icons.info_outline, color: Colors.orange, size: 18), Icon(Icons.info_outline,
const SizedBox(width: 8), color: Colors.orange, size: 18),
SizedBox(width: 8),
Expanded( Expanded(
child: Text( 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.', 'Creating an account will notify the customer and request balance to start water delivery.',
style: TextStyle(color: Colors.black87, fontSize: 13), style: TextStyle(fontSize: 13),
), ),
) )
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: selectedCustomerId == null
// Add your submission logic here ? 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( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF9375E8), backgroundColor: const Color(0xFF9375E8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), minimumSize: const Size(double.infinity, 50),
minimumSize: Size(double.infinity, 50), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
), ),
child: Text('Create Account'), child: const Text('Create Account'),
) )
], ],
), ),
), ),
); );
} }
} }

@ -1,10 +1,10 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/financials/add_transactions.dart'; import 'package:supplier_new/financials/add_transactions.dart';
import 'package:supplier_new/financials/building_transactions_details.dart'; import 'package:supplier_new/financials/building_transactions_details.dart';
import 'package:supplier_new/financials/create_credit_accounts.dart'; import 'package:supplier_new/financials/create_credit_accounts.dart';
class FinancialMainScreen extends StatefulWidget { class FinancialMainScreen extends StatefulWidget {
const FinancialMainScreen({super.key}); const FinancialMainScreen({super.key});
@ -22,47 +22,22 @@ Color _amountColor(Map<String, dynamic> txn) {
if (amt.startsWith('-')) return const Color(0xFFE2483D); // red if (amt.startsWith('-')) return const Color(0xFFE2483D); // red
return const Color(0xFF2D2E30); // default/dark text return const Color(0xFF2D2E30); // default/dark text
} }
class _FinancialMainScreenState extends State<FinancialMainScreen> class _FinancialMainScreenState extends State<FinancialMainScreen>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
late TabController _tabController; late TabController _tabController;
final transactions = [ /// API transactions
{ bool isLoadingTransactions = true;
"name": "My Home Bhooja", List<Map<String, dynamic>> transactions = [];
"date": "21 August", bool isLoadingCreditAccounts = true;
"amount": "+ ₹2,580", List<Map<String, dynamic>> creditAccounts = [];
"color": Colors.green,
"status": "success", double receivableBalance = 0;
}, double advanceBalance = 0;
{
"name": "Ramakrishna", int activeAccounts = 0;
"date": "20 August", int overdueAccounts = 0;
"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",
},
];
@override @override
void initState() { void initState() {
@ -71,6 +46,77 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
_tabController.addListener(() { _tabController.addListener(() {
setState(() {}); // rebuilds FAB visibility when tab changes setState(() {}); // rebuilds FAB visibility when tab changes
}); });
fetchTransactions();
fetchCreditAccounts();
}
Future<void> 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<String> customerIds = [];
final accounts = list.map<Map<String, dynamic>>((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 @override
@ -79,6 +125,41 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
super.dispose(); super.dispose();
} }
/// FETCH TRANSACTIONS API (based on your response)
Future<void> 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<Map<String, dynamic>>((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() { Widget fab() {
final index = _tabController.index; final index = _tabController.index;
@ -107,26 +188,22 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
final index = _tabController.index; final index = _tabController.index;
if (index == 0) { if (index == 0) {
// Tab 1 Navigate to Create Account
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => AddTransactionScreen(), builder: (_) => AddTransactionScreen(),
), ),
); );
} else if (index == 1) {
// Tab 2 Navigate to Transactions
/*Navigator.push(
context,
MaterialPageRoute(builder: (_) => const TransactionsPage()),
);*/
} }
} }
Widget Transactions() { Widget Transactions() {
if (isLoadingTransactions) {
return const Center(child: CircularProgressIndicator());
}
return Container( return Container(
color: Color(0XFFFFFFFF), color: const Color(0XFFFFFFFF),
child: Column( child: Column(
children: [ children: [
// Filters row // Filters row
@ -144,39 +221,40 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
), ),
Expanded( 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, padding: EdgeInsets.zero,
itemCount: transactions.length, itemCount: transactions.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final txn = transactions[index]; final txn = transactions[index];
return ListTile( return ListTile(
dense: true, // dense: true,
minVerticalPadding: 0, minVerticalPadding: 0,
visualDensity: const VisualDensity(vertical: 0, horizontal: 0), // tighter visualDensity: const VisualDensity(vertical: 0, horizontal: 0),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0), // no extra top/bottom contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
horizontalTitleGap: 8, horizontalTitleGap: 8,
minLeadingWidth: 40, minLeadingWidth: 40,
leading: const CircleAvatar( leading: Image.asset('images/avatar.png', width: 30, height: 30),
radius: 18,
backgroundColor: Colors.blue,
child: Icon(Icons.person, color: Colors.white, size: 18),
),
title: Text( title: Text(
txn["name"].toString(), txn["name"].toString(),
style: fontTextStyle(14, Color(0xFF2D2E30), FontWeight.w500) style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500)
.copyWith(height: 1.1), // tighter line height .copyWith(height: 1.1),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
subtitle: Text( subtitle: Text(
txn["date"].toString(), txn["date"].toString(),
style: fontTextStyle(12, Color(0xFF939495), FontWeight.w400) style: fontTextStyle(12, const Color(0xFF939495), FontWeight.w400)
.copyWith(height: 1.0), // tighter line height .copyWith(height: 1.0),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
trailing: Column( trailing: Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -189,7 +267,7 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.error, color: Color(0xFF9F9F9F), size: 14), // gray const Icon(Icons.error, color: Color(0xFF9F9F9F), size: 14),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
"Failed", "Failed",
@ -199,59 +277,53 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
), ),
], ],
), ),
); );
}, },
), ),
) )
], ],
), ),
); );
} }
Widget CreditAccounts() { Widget CreditAccounts() {
if (isLoadingCreditAccounts) {
return const Center(child: CircularProgressIndicator());
}
return SingleChildScrollView( return SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
children: [ children: [
// Account Summary /// Create Account Button (UNCHANGED)
Align( Align(
alignment: Alignment.centerRight, // aligns button to the right alignment: Alignment.centerRight,
child: OutlinedButton( child: OutlinedButton(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: Color(0xFFFFFFFF), foregroundColor: Colors.white,
backgroundColor: Color(0xFF000000), backgroundColor: Colors.black,
side: BorderSide(color: Color(0xFF000000)),
padding: EdgeInsets.symmetric(
vertical: 10, horizontal: 16), // padding around content
), ),
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(builder: (_) => CreateCreditAccountScreen()),
builder: (_) => CreateCreditAccountScreen(),
),
); );
}, },
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
// shrink button to fit content
children: [ children: [
Image.asset('images/plus.png', height: 20, width: 20), Image.asset('images/plus.png', height: 20),
SizedBox(width: 4), const SizedBox(width: 6),
Text( const Text("Create Account"),
"Create Account",
style: fontTextStyle(12, const Color(0xFFFFFFFF), FontWeight.w400),
),
], ],
), ),
), ),
), ),
// Account Summary (ONLY header + count) const SizedBox(height: 12),
/// Account Summary
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -259,24 +331,17 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Expanded( const Expanded(child: Text("Account Summary")),
child: Text(
"Account Summary",
style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w500),
),
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text( Text(
"05", creditAccounts.length.toString(),
style: fontTextStyle(24, const Color(0xFF0D3771), FontWeight.w500), style: fontTextStyle(24, const Color(0xFF0D3771), FontWeight.w500),
), ),
const SizedBox(height: 2),
Text( Text(
"4 active, 1 overdue", "$activeAccounts active, $overdueAccounts overdue",
style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400), style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400),
), ),
], ],
@ -287,143 +352,102 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
const SizedBox(height: 12), const SizedBox(height: 12),
// NEW: Balances in a separate container/card /// Balances
Container( Row(
padding: const EdgeInsets.all(16), children: [
_balanceBox(
child: Row( title: "Receivable Balance",
children: [ amount: receivableBalance,
Expanded( color: Colors.red,
child: Container( ),
padding: const EdgeInsets.all(12), const SizedBox(width: 12),
decoration: BoxDecoration( _balanceBox(
borderRadius: BorderRadius.circular(12), title: "Advance Balance",
color: const Color(0xffFFFFFF), amount: advanceBalance,
), color: Colors.green,
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)),
],
),
),
),
],
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Search Bar /// Accounts List
Container( creditAccounts.isEmpty
padding: ? const Center(child: Text("No Credit Accounts"))
const EdgeInsets.symmetric(horizontal: 12, vertical: 6), : Column(
decoration: BoxDecoration( children: creditAccounts.map((acc) {
color: Colors.white, return _accountTile(
borderRadius: BorderRadius.circular(12), name: acc["name"] ?? "",
), id: acc["id"] ?? "",
child: Row( status: acc["status"] ?? "",
children: [ transactionId: acc["transactionId"] ?? "", // FIX
const Icon(Icons.search, color: Colors.grey), orders: acc["orders"] ?? "",
const SizedBox(width: 8), balance: acc["balance"] ?? "₹0",
const Expanded( credit: acc["credit"] ?? "₹0",
child: TextField( lastPayment: acc["lastPayment"] ?? "",
decoration: InputDecoration( balanceColor: (acc["balanceValue"] ?? 0) < 0
hintText: "Search", ? Colors.red
border: InputBorder.none, : Colors.green,
), creditColor: Colors.red,
), );
), }).toList(),
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),
],
),
), ),
],
),
),
);
}
const SizedBox(height: 20), Widget _balanceBox({
required String title,
// Accounts List required double amount,
_accountTile( required Color color,
name: "SVG Towers", }) {
status: "active", return Expanded(
orders: "48 orders", child: Container(
balance: "₹10,000", padding: const EdgeInsets.all(12),
credit: "₹10,000", decoration: BoxDecoration(
lastPayment: "₹5,000 | 12 Aug", borderRadius: BorderRadius.circular(12),
balanceColor: Colors.red, color: Colors.white,
creditColor: Colors.green, ),
), child: Column(
_accountTile( crossAxisAlignment: CrossAxisAlignment.start,
name: "Canvas Creations", children: [
status: "active", Text(title),
orders: "32 orders", const SizedBox(height: 4),
balance: "₹7,500", Text("${amount.toStringAsFixed(0)}",
credit: "₹7,500", style: TextStyle(color: color, fontSize: 16)),
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 _accountTile({ Widget _accountTile({
required String name, required String name,
required String status, required String status,
required String id,
required String orders, required String orders,
required String balance, required String balance,
required String credit, required String credit,
required String lastPayment, required String lastPayment,
required Color balanceColor, required Color balanceColor,
required Color creditColor, required Color creditColor,
required String transactionId,
}) { }) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
if (status == "paid_by_customer") return;
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( 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<FinancialMainScreen>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Name + Status Row(
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, ), ), ) ], ), children: [
const SizedBox(height: 4), Expanded(
Text( child: Text(id,
orders, style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500)),
style: const TextStyle(color: Colors.grey, fontSize: 12), ),
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), const SizedBox(height: 12),
// Balance - Credit - Last Payment
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -456,27 +507,18 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
const Text("Available Balance", const Text("Available Balance",
style: TextStyle(fontSize: 12, color: Colors.black54)), style: TextStyle(fontSize: 12, color: Colors.black54)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(balance,
balance, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: balanceColor)),
style: TextStyle(fontSize: 14,
fontWeight: FontWeight.bold,
color: balanceColor),
),
], ],
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text("Available Credit", const Text("Available Credit",
style: TextStyle( style: TextStyle(fontSize: 12, color: Colors.black54)),
fontSize: 12, color: Colors.black54)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(credit,
credit, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: creditColor)),
style: TextStyle(fontSize: 14,
fontWeight: FontWeight.bold,
color: creditColor),
),
], ],
), ),
Column( Column(
@ -485,15 +527,38 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
const Text("Last Payment", const Text("Last Payment",
style: TextStyle(fontSize: 12, color: Colors.black54)), style: TextStyle(fontSize: 12, color: Colors.black54)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(lastPayment,
lastPayment, style: fontTextStyle(12, const Color(0XFFF1F1F1), FontWeight.w500)),
style: fontTextStyle(
12, 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<FinancialMainScreen>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Color(0XFFFFFFFF),
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
backgroundColor: Colors.white, backgroundColor: Colors.white,
@ -517,27 +583,22 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
return TabBar( return TabBar(
controller: _tabController, controller: _tabController,
indicatorColor: Colors.transparent, indicatorColor: Colors.transparent,
// remove underline
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
isScrollable: false, isScrollable: false,
overlayColor: MaterialStateProperty.all(Colors.transparent), overlayColor: MaterialStateProperty.all(Colors.transparent),
// equal width
tabs: List.generate(2, (index) { tabs: List.generate(2, (index) {
final labels = ['Transactions', 'Credit Accounts']; final labels = ['Transactions', 'Credit Accounts'];
final isSelected = _tabController.index == index; final isSelected = _tabController.index == index;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? const Color(0XFFF1F1F1) : Colors color: isSelected ? const Color(0XFFF1F1F1) : Colors.transparent,
.transparent,
borderRadius: BorderRadius.circular(27), borderRadius: BorderRadius.circular(27),
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
labels[index], labels[index],
style: fontTextStyle( style: fontTextStyle(12, const Color(0XFF000000), FontWeight.w600),
12, Color(0XFF000000), FontWeight.w600,
),
), ),
); );
}), }),
@ -550,15 +611,10 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
body: TabBarView( body: TabBarView(
controller: _tabController, controller: _tabController,
children: [ children: [
// Tab 1: Transactions
Transactions(), Transactions(),
CreditAccounts(),
// Tab 2: Credit Accounts
CreditAccounts()
], ],
), ),
floatingActionButton: _tabController.index == 0 ? fab() : null, floatingActionButton: _tabController.index == 0 ? fab() : null,
); );
} }
@ -579,11 +635,7 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
), ),
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
const Icon( const Icon(Icons.keyboard_arrow_down, size: 18, color: Colors.black),
Icons.keyboard_arrow_down, // Down arrow
size: 18,
color: Colors.black,
),
], ],
), ),
backgroundColor: Colors.white, backgroundColor: Colors.white,
@ -592,10 +644,8 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
onSelected: (val) { onSelected: (val) {},
},
), ),
); );
} }
}
}

@ -15,12 +15,160 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
//int advance =0; //int advance =0;
double amountToPayAfterDelivery = 0.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<Map<String, String>> paymentTypes = [
{"key": "advance", "label": "Advance"},
{"key": "after_delivery", "label": "After Delivery"},
{"key": "credit", "label": "Credit"},
];
String? advanceError;
String? creditError;
@override @override
void initState() { void initState() {
// TODO: implement initState // TODO: implement initState
tankerPriceController.text = '${widget.order.quoted_amount}';
super.initState(); super.initState();
} }
Future<void> 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<String>(
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<String>(
title: Text("After Delivery",
style:
fontTextStyle(14, Colors.black, FontWeight.w400)),
value: "after_delivery",
groupValue: paymentMode,
onChanged: (v) {
setStateSB(() => paymentMode = v!);
},
),
RadioListTile<String>(
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -177,13 +325,74 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * .011, 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", "Tanker Price",
"${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}", "${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}",
"images/financialsBottomIcon.png", "images/financialsBottomIcon.png",
"", "",
"", "",
""), ""),*/
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * .02, height: MediaQuery.of(context).size.height * .02,
), ),
@ -194,41 +403,30 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
"Capacity", "Capacity",
"${widget.order.capacity}", "${widget.order.capacity}",
"images/capacity.png", "images/capacity.png",
), ),
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * .02, height: MediaQuery.of(context).size.height * .02,
), ),
_detailTwoRow( _detailTwoRow(
"Start Date", "Start Date",
"${widget.order.averageTime}", "${widget.order.startDate}",
"images/time.png", "images/time.png",
"End Date", "End Date",
"${widget.order.averageTime}", "${widget.order.endDate}",
"images/advance.png", "images/time.png",
), ),
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * .02, height: MediaQuery.of(context).size.height * .02,
), ),
_detailTwoRow( _detailTwoRow("Frequency", "${widget.order.quantity}",
"Frequency", "images/quantity.png", "", "", ""),
"${widget.order.quantity}",
"images/quantity.png",
"",
"",
""),
], ],
), ),
), ),
SizedBox(
height: MediaQuery.of(context).size.height * .008,
),
/// 🔹 Additional Details /// 🔹 Additional Details
Padding( Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 16), padding: EdgeInsets.fromLTRB(16, 16, 16, 16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -240,14 +438,8 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * .011, height: MediaQuery.of(context).size.height * .011,
), ),
Text( Text('Total tankers for this plan are ${widget.order.dates.length}', style: fontTextStyle(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, " 12, const Color(0xFF939495), FontWeight.w400),)
"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),
),
], ],
)), )),
@ -265,9 +457,31 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * .011, height: MediaQuery.of(context).size.height * .011,
), ),
_detailRow("Tanker Price", ValueListenableBuilder<TextEditingValue>(
"${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}"), valueListenable: tankerPriceController,
/* SizedBox( 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, height: MediaQuery.of(context).size.height * .004,
), ),
@ -310,14 +524,13 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
Expanded( Expanded(
child: OutlinedButton( child: OutlinedButton(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: Color(0XFF000000), foregroundColor: Color(0XFFE2483D),
backgroundColor: Color(0xFFF1F1F1), backgroundColor: Colors.white,
side: BorderSide(color: Color(0xFFF1F1F1)), side: BorderSide(color: Color(0XFFE2483D)),
padding: padding: EdgeInsets.symmetric(vertical: 10),
EdgeInsets.symmetric(vertical: 10), // uniform height
), ),
onPressed: () { onPressed: () {
Navigator.push( /*Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => EditPlanRequests( builder: (_) => EditPlanRequests(
@ -325,15 +538,16 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
status: widget.status, status: widget.status,
), ),
), ),
); );*/
Navigator.pop(context);
}, },
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Image.asset('images/edit.png', height: 20, width: 20), Image.asset('images/cross.png', height: 20, width: 20),
SizedBox(width: 4), SizedBox(width: 4),
Text( Text(
"Edit Plan", "Cancel",
style: fontTextStyle( style: fontTextStyle(
14, const Color(0XFF000000), FontWeight.w400), 14, const Color(0XFF000000), FontWeight.w400),
), ),
@ -345,12 +559,12 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
Expanded( Expanded(
child: OutlinedButton( child: OutlinedButton(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: Color(0XFFE2483D), foregroundColor: Color(0XFFFFFFFF),
backgroundColor: Colors.white, backgroundColor: Color(0XFFE2483D),
side: BorderSide(color: Color(0XFFE2483D)), side: BorderSide(color: Color(0XFFE2483D)),
padding: EdgeInsets.symmetric(vertical: 10), padding: EdgeInsets.symmetric(vertical: 10),
), ),
onPressed: ()async { onPressed: () async {
/*AppSettings.preLoaderDialog(context); /*AppSettings.preLoaderDialog(context);
bool isOnline = await AppSettings.internetConnectivity(); bool isOnline = await AppSettings.internetConnectivity();
@ -390,12 +604,12 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ 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), SizedBox(width: 4),
Text( Text(
"Reject", "Reject",
style: fontTextStyle( style: fontTextStyle(
14, const Color(0XFF000000), FontWeight.w400), 14, const Color(0XFFFFFFFF), FontWeight.w400),
), ),
], ],
), ),
@ -414,33 +628,57 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
bool isOnline = await AppSettings.internetConnectivity(); bool isOnline = await AppSettings.internetConnectivity();
if (isOnline) { if (!isOnline) {
var payload = new Map<String, dynamic>(); Navigator.of(context, rootNavigator: true).pop();
/*payload["supplierId"] = AppSettings.supplierId; AppSettings.longFailedToast("Please Check internet");
payload["amount"] = int.parse(widget.order.quoted_amount); return;
payload["delivery_charges"] = advance;*/ }
payload["action"] = 'accept';
if (!isPaymentValid()) {
Navigator.of(context, rootNavigator: true).pop();
AppSettings.longFailedToast(
paymentMode == "advance"
? "Please enter advance amount"
: "Please enter credit limit",
);
return;
}
try {
final Map<String, dynamic> 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( bool status = await AppSettings.acceptPlanRequests(
payload, widget.order.dbId); payload,
widget.order.dbId,
);
try { Navigator.of(context, rootNavigator: true).pop();
if (status) {
Navigator.of(context,rootNavigator: true).pop(); if (status) {
Navigator.pop(context, true); Navigator.pop(context, true);
} } else {
else{ AppSettings.longFailedToast("Accept of plan request Failed");
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Accept of plan request Failed");
}
} catch (e) {
Navigator.of(context,rootNavigator: true).pop();
print(e);
} }
} } catch (e) {
else{ Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context,rootNavigator: true).pop(); AppSettings.longFailedToast("Something went wrong");
AppSettings.longFailedToast("Please Check internet");
} }
}, },
child: Row( child: Row(
@ -462,6 +700,107 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
); );
} }
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 /// 🔹 Helper widget for rows
Widget _detailRow(String title, String value) { Widget _detailRow(String title, String value) {
return Padding( return Padding(
@ -587,17 +926,19 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
"REASON FOR REJECTION", "REASON FOR REJECTION",
style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600), style: fontTextStyle(
10, const Color(0XFF2D2E30), FontWeight.w600),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// reasons // reasons
...reasons.map( ...reasons.map(
(reason) => RadioListTile<String>( (reason) => RadioListTile<String>(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: Text( title: Text(
reason, reason,
style: fontTextStyle(14, const Color(0XFF2D2E30), FontWeight.w400), style: fontTextStyle(
14, const Color(0XFF2D2E30), FontWeight.w400),
), ),
value: reason, value: reason,
activeColor: const Color(0XFFE2483D), activeColor: const Color(0XFFE2483D),
@ -614,14 +955,15 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
onPressed: () => Navigator.pop(context, null), onPressed: () => Navigator.pop(context, null),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14), padding: const EdgeInsets.symmetric(vertical: 14),
side: const BorderSide(color:Color(0XFFFFFFFF)), side: const BorderSide(color: Color(0XFFFFFFFF)),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), ),
), ),
child: Text( child: Text(
"Cancel", "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<AcceptPlanRequests> {
), ),
child: Text( child: Text(
"Reject Request", "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<AcceptPlanRequests> {
}, },
); );
} }
} }

@ -1,16 +1,94 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/plans/plan_details.dart'; import 'package:supplier_new/plans/plan_details.dart';
import 'package:supplier_new/plans/plan_requests.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'; 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<String, dynamic> 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<bool> showAcceptConfirmDialog(BuildContext context) async {
return await showDialog<bool>(
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 { class AllPlans extends StatefulWidget {
final String navigationFrom; final String navigationFrom;
AllPlans({ const AllPlans({super.key, required this.navigationFrom});
super.key,
required this.navigationFrom,
});
@override @override
State<AllPlans> createState() => _AllPlansState(); State<AllPlans> createState() => _AllPlansState();
@ -19,60 +97,85 @@ class AllPlans extends StatefulWidget {
class _AllPlansState extends State<AllPlans> { class _AllPlansState extends State<AllPlans> {
final TextEditingController searchController = TextEditingController(); final TextEditingController searchController = TextEditingController();
bool isLoading = true;
/// UI MODELS
List<PlansModel> plans = [];
/// RAW API DATA (IMPORTANT)
List<Map<String, dynamic>> rawPlans = [];
int activeCount = 0;
int boreCount = 0;
int drinkingCount = 0;
@override
void initState() {
super.initState();
fetchPlans();
}
/// =====================
/// FETCH API
/// =====================
Future<void> fetchPlans() async {
try {
final response = await AppSettings.getAcceptedRecurringBookings();
final decoded = jsonDecode(response);
final List data = decoded["data"] ?? [];
/// FILTER ONLY payment_completed
rawPlans = List<Map<String, dynamic>>.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<PlansModel> 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( return Scaffold(
backgroundColor: const Color(0XFFFFFFFF), backgroundColor: Colors.white,
appBar: widget.navigationFrom.toString().toLowerCase()=='dashboard'?SearchPlanAppBar( appBar: widget.navigationFrom.toLowerCase() == 'dashboard'
? SearchPlanAppBar(
controller: searchController, controller: searchController,
onBack: () => Navigator.pop(context), onBack: () => Navigator.pop(context),
onHelp: () { onHelp: () {},
print("Help tapped"); )
}, : null,
):null, body: isLoading
body: SingleChildScrollView( ? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
/// 🔹 Grey top section /// 🔹 TOP SECTION
Container( Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Color(0XFFF2F2F2), color: Color(0XFFF2F2F2),
@ -81,48 +184,42 @@ class _AllPlansState extends State<AllPlans> {
bottomRight: Radius.circular(24), bottomRight: Radius.circular(24),
), ),
), ),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), padding: const EdgeInsets.all(24),
child: Column( child: Column(
children: [ children: [
Text( Text(
"05", activeCount.toString().padLeft(2, '0'),
style: fontTextStyle(64, const Color(0XFF2D2E30), FontWeight.w700), style: fontTextStyle(
64, const Color(0XFF2D2E30), FontWeight.w700),
), ),
Text( Text(
"Active Plans", "Active Plans",
style: fontTextStyle(24, const Color(0XFF2D2E30), FontWeight.w600), style: fontTextStyle(
24, const Color(0XFF2D2E30), FontWeight.w600),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
/// Bore Water + Drinking Water
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
PlanCategoryCard( PlanCategoryCard(
image: Image.asset( image: Image.asset('images/bore-water.png',
'images/bore-water.png', height: 40, width: 40),
fit: BoxFit.contain, value: boreCount.toString().padLeft(2, '0'),
height: 40,
width: 40,
),
value: "02",
label: "Bore Water", label: "Bore Water",
), ),
PlanCategoryCard( PlanCategoryCard(
image: Image.asset( image: Image.asset('images/drinking-water.png',
'images/drinking-water.png', height: 40, width: 40),
height: 40, value:
width: 40, drinkingCount.toString().padLeft(2, '0'),
fit: BoxFit.contain,
),
value: "03",
label: "Drinking Water", label: "Drinking Water",
), ),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
/// Button
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
@ -131,20 +228,20 @@ class _AllPlansState extends State<AllPlans> {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
), ),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.all(16),
horizontal: 16, vertical: 16),
), ),
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const PlanRequestsPage()), builder: (_) =>
const PlanRequestsPage()),
); );
}, },
child: Text( child: Text(
"View Plan Requests", "View Plan Requests",
style: fontTextStyle( style: fontTextStyle(
16, const Color(0XFFFFFFFF), FontWeight.w500), 16, Colors.white, FontWeight.w500),
), ),
), ),
), ),
@ -152,48 +249,39 @@ class _AllPlansState extends State<AllPlans> {
), ),
), ),
/// 🔹 PLAN LIST
/// 🔹 White bottom section (filters + list) isLoading
Container( ? Center(
color: Color(0XFFFFFFFF), child: CircularProgressIndicator())
child: Column( : (plans.isEmpty
children: [ ? Center(
const SizedBox(height: 12), child: Padding(
padding:
/// Filters Row const EdgeInsets.symmetric(
SingleChildScrollView( vertical: 50),
scrollDirection: Axis.horizontal, child: Text(
padding: const EdgeInsets.symmetric(horizontal: 12), 'No Data Available',
child: Row( style: fontTextStyle(
children: [ 12,
FilterChipWidget(label: "Building"), const Color(0XFF2D2E30),
const SizedBox(width: 8), FontWeight.w500),
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);
},
),
],
), ),
), )
: 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<AllPlans> {
} }
} }
/// Curve clipper for smooth transition /// =====================
class SmallCurveClipper extends CustomClipper<Path> { /// PLAN CARD
@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<Path> 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
class PlansCard extends StatelessWidget { class PlansCard extends StatelessWidget {
final PlansModel delivery; final PlansModel delivery;
final Map<String, dynamic> planJson;
const PlansCard({super.key, required this.delivery}); const PlansCard({
super.key,
required this.delivery,
required this.planJson,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isActive = delivery.status == "Active"; bool isActive = delivery.status == "Active";
return GestureDetector( return GestureDetector(
onTap: (){ onTap: () {
Navigator.push( if (delivery.status != "Active") return;
context,
MaterialPageRoute( Navigator.push(
builder: (context) => PlanDetails(), context,
), MaterialPageRoute(
); builder: (_) => PlanDetails(plan: planJson),
}, ),
);
},
child: Container( child: Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE5E5E5)), border: Border.all(color: const Color(0xFFE5E5E5)),
boxShadow: [ color: Colors.white,
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 5,
offset: const Offset(0, 2),
)
],
), ),
child: Column( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
crossAxisAlignment: CrossAxisAlignment.start, Container(
children: [ padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
// Status Chip decoration: BoxDecoration(
Container( color: isActive ? const Color(0xFFE9F9EE) : Colors.grey.shade200,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), borderRadius: BorderRadius.circular(6),
decoration: BoxDecoration( ),
color: isActive ? const Color(0xFFE9F9EE) : const Color(0xFFF2F2F2), child: Text(
borderRadius: BorderRadius.circular(6), delivery.status,
border: Border.all( style: TextStyle(
color: isActive ? const Color(0xFF3BB273) : Colors.grey.shade400, fontSize: 12,
), color: isActive
), ? const Color(0xFF3BB273)
child: Text( : Colors.grey,
delivery.status,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: isActive ? const Color(0xFF3BB273) : Colors.grey.shade600,
),
), ),
), ),
const SizedBox(height: 8), ),
const SizedBox(height: 8),
// Apartment + Price Row Row(
Row( mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
children: [ Text(delivery.apartment,
Text(
delivery.apartment,
style: const TextStyle( style: const TextStyle(
fontSize: 15, fontSize: 15, fontWeight: FontWeight.w600)),
fontWeight: FontWeight.w600, Text(delivery.price,
color: Color(0xFF2A2A2A),
),
),
Text(
delivery.price,
style: const TextStyle( style: const TextStyle(
fontSize: 15, fontSize: 15, fontWeight: FontWeight.w600)),
fontWeight: FontWeight.w600, ],
color: Color(0xFF2A2A2A), ),
), const SizedBox(height: 4),
),
],
),
const SizedBox(height: 4),
// Liters & Advance Row(
Row( mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
children: [ Text(delivery.liters,
Text(
delivery.liters,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14, color: Color(0xFF7B5AF4))),
fontWeight: FontWeight.w500, Text(delivery.advance,
color: Color(0xFF7B5AF4), style: const TextStyle(fontSize: 12)),
), ],
), ),
Text( Visibility(
delivery.advance, visible: delivery.status=='payment_completed',
style: const TextStyle( child: Row(
fontSize: 12, children: [
color: Color(0xFF646566), GestureDetector(
onTap: (){
},
child:
ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.transparent,
BlendMode.multiply),
child: Image
.asset(
'images/wrong_button.png',
width:
44,
height:
44,
), ),
), ),
], ),
), SizedBox(
const SizedBox(height: 12), width: MediaQuery.of(context)
.size
.width *
.012,
),
GestureDetector(
onTap: () async {
final confirmed = await showAcceptConfirmDialog(context);
if (!confirmed) return;
// Deliveries row with background try {
Container( final res = await AppSettings.respondRecurringBooking(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), bookingId: planJson["_id"],
decoration: BoxDecoration( supplierId: planJson["my_supplier"]["supplierId"],
color: const Color(0xFFF8F6FF), action: "accept",
borderRadius: BorderRadius.circular(8), );
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( const SizedBox(height: 12),
fontSize: 13,
fontWeight: FontWeight.w500, Container(
color: Color(0xFF2A2A2A), padding: const EdgeInsets.all(8),
), decoration: BoxDecoration(
), color: const Color(0xFFF8F6FF),
Text( borderRadius: BorderRadius.circular(8),
delivery.frequency, ),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(delivery.deliveries,
style: const TextStyle(fontSize: 13)),
Text(delivery.frequency,
style: const TextStyle( style: const TextStyle(
fontSize: 13, fontSize: 13, color: Color(0xFF7B5AF4))),
fontWeight: FontWeight.w500, ],
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)),
],
);
}
}

@ -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<SupplierCalendar> createState() => _SupplierCalendarState();
}
class _SupplierCalendarState extends State<SupplierCalendar> {
late DateTime _focusedDay;
late DateTime _firstDay;
late DateTime _lastDay;
Map<DateTime, List<Map<String, dynamic>>> calendarEvents = {};
DateTime? _selectedDay;
bool isLoading = true;
@override
void initState() {
super.initState();
final now = DateTime.now();
_focusedDay = now.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<void> fetchOrdersFromApi() async {
try {
final response = await AppSettings.getSupplierBookings();
final decoded = jsonDecode(response);
if (decoded == null || decoded['data'] == null) {
setState(() => isLoading = false);
return;
}
final List<dynamic> orders = decoded['data'];
calendarEvents.clear();
for (var order in orders) {
String? originalDate = order["date"];
String? newDate = order["reScheduleDateOfDelivery"];
String? resStatus = order["rescheduleOrderStatus"];
bool isRescheduled =
newDate != null && newDate.isNotEmpty && resStatus != null && resStatus.isNotEmpty;
// ===================== ORIGINAL DATE EVENT =====================
if (originalDate != null && order["orderStatus"] != "cancelled") {
try {
DateTime d = DateTime.parse(originalDate);
DateTime key = DateTime(d.year, d.month, d.day);
calendarEvents.putIfAbsent(key, () => []);
calendarEvents[key]!.add({
"status": isRescheduled ? "rescheduled_from" : "delivery",
"_id": order["_id"],
"supplierName": order["supplierName"] ?? "Supplier",
"capacity": order["capacity"] ?? "",
"time": order["time"] ?? "",
"originalDate": originalDate,
"newDate": newDate,
});
} catch (e) {}
}
// ===================== NEW RESCHEDULED DATE EVENT =====================
if (isRescheduled) {
try {
DateTime d2 = DateTime.parse(newDate);
DateTime key2 = DateTime(d2.year, d2.month, d2.day);
calendarEvents.putIfAbsent(key2, () => []);
calendarEvents[key2]!.add({
"status": "rescheduled_to",
"_id": order["_id"],
"supplierName": order["supplierName"] ?? "Supplier",
"capacity": order["capacity"] ?? "",
"time": order["time"] ?? "",
"originalDate": originalDate,
"newDate": newDate,
});
} catch (e) {}
}
// ===================== CANCELLED EVENT =====================
if (order["orderStatus"] == "cancelled") {
try {
DateTime d = DateTime.parse(originalDate!);
DateTime key = DateTime(d.year, d.month, d.day);
calendarEvents.putIfAbsent(key, () => []);
calendarEvents[key]!.add({
"status": "cancelled",
"_id": order["_id"],
"supplierName": order["supplierName"] ?? "Supplier",
"capacity": order["capacity"] ?? "",
"time": order["time"] ?? "",
});
} catch (e) {}
}
}
setState(() => isLoading = false);
} catch (e) {
setState(() => isLoading = false);
}
}
// ===========================================================================
// CANCEL ORDER API
// ===========================================================================
/*Future<void> cancelDelivery(String id) async {
try {
await AppSettings.cancelPlanOrder(id); // your endpoint
await fetchOrdersFromApi();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Order cancelled successfully")));
} catch (e) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Cancel failed")));
}
}*/
/*void _confirmCancel(String orderId) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text("Cancel Delivery"),
content: Text("Are you sure you want to cancel this delivery?"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context), child: Text("No")),
TextButton(
onPressed: () {
Navigator.pop(context);
cancelDelivery(orderId);
},
child: Text("Yes, Cancel", style: TextStyle(color: Colors.red))),
],
),
);
}*/
// ===========================================================================
// HELPER FUNCTIONS
// ===========================================================================
String formatShort(String date) {
try {
final d = DateTime.parse(date);
return "${d.day} ${monthShort[d.month - 1]}";
} catch (e) {
return date;
}
}
List<String> monthShort = [
"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec"
];
Widget _buildStatusIcon(String status) {
if (status == "rescheduled_from") {
return Icon(Icons.subdirectory_arrow_left, color: Colors.orange);
}
if (status == "rescheduled_to") {
return Icon(Icons.subdirectory_arrow_right, color: Colors.deepOrange);
}
if (status == "cancelled") {
return Icon(Icons.cancel, color: Colors.red);
}
return Icon(Icons.local_shipping, color: Colors.blue);
}
String _buildStatusText(Map<String, dynamic> d) {
String status = d["status"];
String? originalDate = d["originalDate"];
String? newDate = d["newDate"];
if (status == "rescheduled_from") {
return "Rescheduled from this date → ${formatShort(newDate!)}";
}
if (status == "rescheduled_to") {
return "Rescheduled to this date (from ${formatShort(originalDate!)})";
}
if (status == "cancelled") {
return "Cancelled";
}
return status;
}
// ===========================================================================
// OPEN RESCHEDULE CALENDAR
// ===========================================================================
Future<void> openRescheduleCalendar(Map<String, dynamic> delivery) async {
DateTime now = DateTime.now();
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: now.add(Duration(days: 1)),
firstDate: now.add(Duration(days: 1)),
lastDate: DateTime(now.year + 1, 12, 31),
);
if (pickedDate == null) return;
String formatted =
"${pickedDate.year}-${pickedDate.month.toString().padLeft(2,'0')}-${pickedDate.day.toString().padLeft(2,'0')}";
//await sendRescheduleToServer(delivery["_id"], formatted);
}
/*Future<void> sendRescheduleToServer(String id, String newDate) async {
try {
final payload = {"reScheduleDateOfDelivery": newDate};
await AppSettings.rescheduleOrder(id, payload);
await fetchOrdersFromApi();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Delivery rescheduled to $newDate")));
} catch (e) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Reschedule failed")));
}
}
*/
// ===========================================================================
// SHOW DELIVERY LIST
// ===========================================================================
void showDeliveryList(DateTime date) {
final key = DateTime(date.year, date.month, date.day);
final events = calendarEvents[key];
if (events == null || events.isEmpty) return;
bool isPast = date.isBefore(DateTime(
DateTime.now().year, DateTime.now().month, DateTime.now().day));
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) {
final list = events.toList();
return DraggableScrollableSheet(
initialChildSize: 0.9,
maxChildSize: 0.9,
minChildSize: 0.4,
builder: (_, controller) {
return Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
children: [
Row(
children: [
Expanded(
child: Text(
"Select Delivery",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Icon(Icons.close)),
],
),
Expanded(
child: ListView(
controller: controller,
children: list.map((delivery) {
return ListTile(
leading: _buildStatusIcon(delivery["status"]),
title: Text(_buildStatusText(delivery)),
subtitle: Text(
"Capacity: ${delivery["capacity"]}${delivery["time"]}"),
onTap: () {
Navigator.pop(context);
showActionsForSingleDelivery(delivery, isPast);
},
);
}).toList(),
),
)
],
),
);
},
);
},
);
}
// ===========================================================================
// ACTIONS SHEET
// ===========================================================================
void showActionsForSingleDelivery(Map<String, dynamic> delivery, bool isPast) {
String status = delivery["status"];
// ORIGINAL DATE NO ACTIONS
if (status == "rescheduled_from") {
showModalBottomSheet(
context: context,
builder: (_) {
return Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Rescheduled from this date",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
Text("This delivery was moved to another date.",
style: TextStyle(color: Colors.grey)),
],
),
);
},
);
return;
}
// CANCELLED ORDER NO ACTIONS
if (status == "cancelled") {
showModalBottomSheet(
context: context,
builder: (_) =>
Container(
padding: EdgeInsets.all(20),
child: Text("Order was cancelled", style: TextStyle(color: Colors.red)),
),
);
return;
}
// NORMAL OR RESCHEDULED_TO (ACTIONS ENABLED)
showModalBottomSheet(
context: context,
builder: (_) {
return Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("${delivery["supplierName"]}",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
if (!isPast) ...[
ListTile(
leading: Icon(Icons.calendar_month, color: Colors.blue),
title: Text("Reschedule Delivery"),
onTap: () {
Navigator.pop(context);
openRescheduleCalendar(delivery);
},
),
ListTile(
leading: Icon(Icons.delete_forever, color: Colors.red),
title: Text("Cancel Delivery"),
onTap: () {
Navigator.pop(context);
// _confirmCancel(delivery["_id"]);
},
),
],
if (isPast)
Text("Past delivery — actions disabled",
style: TextStyle(color: Colors.grey)),
],
),
);
},
);
}
// ===========================================================================
// BUILD UI
// ===========================================================================
@override
Widget build(BuildContext context) {
if (isLoading) {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
final height = MediaQuery.of(context).size.height;
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
SizedBox(height: 50),
Text(
"${monthShort[_focusedDay.month - 1]} ${_focusedDay.year}",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
Expanded(
child: TableCalendar(
firstDay: _firstDay,
lastDay: _lastDay,
focusedDay: _focusedDay,
headerVisible: false,
calendarFormat: CalendarFormat.month,
rowHeight: (height - 200) / 6 + 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<String, int> 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<String, int> 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,
),
),
]
),
);
},
),
),
),
],
),
);
}
}

@ -2,76 +2,90 @@ import 'package:flutter/material.dart';
import '../common/settings.dart'; import '../common/settings.dart';
class PlanDetails extends StatefulWidget { class PlanDetails extends StatefulWidget {
const PlanDetails({super.key}); final Map<String, dynamic> plan; // 🔥 pass full plan object
const PlanDetails({super.key, required this.plan});
@override @override
State<PlanDetails> createState() => _PlanDetailsState(); State<PlanDetails> createState() => _PlanDetailsState();
} }
class _PlanDetailsState extends State<PlanDetails> { class _PlanDetailsState extends State<PlanDetails> {
final List<Map<String, dynamic>> deliveries = [ late List<DateTime> deliveryDates;
{
"status": "Pending", @override
"quantity": "10,000 L", void initState() {
"type": "Drinking water", super.initState();
"time": "12:30 AM, Tomorrow", deliveryDates = (widget.plan["dates"] as List)
"button": "Assign Tanker", .map((d) => DateTime.parse(d))
}, .toList();
{ }
"status": "Pending",
"quantity": "10,000 L",
"type": "Drinking water",
"time": "12:30 AM, Tomorrow",
"driver": "TS 04 J 8394",
"button": "Assign Driver",
},
];
Widget _buildInfoCard(String value, String label, {Color? color}) { // ================= INFO CARD =================
Widget _buildInfoCard(String value, String label, {Color? bg}) {
return Expanded( return Expanded(
child: Container( child: Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color ?? const Color(0xFFF7F7F7), color: bg ?? const Color(0xFFF7F7F7),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Column( child: Column(
children: [ children: [
Text(value, style: fontTextStyle(18, Colors.black, FontWeight.w600)), Text(value,
style: fontTextStyle(18, Colors.black, FontWeight.w600)),
const SizedBox(height: 4), 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) { // ================= DELIVERY CARD =================
return Padding( Widget _buildDeliveryCard(DateTime date) {
padding: const EdgeInsets.symmetric(horizontal: 4), final now = DateTime.now();
child: Container( String status;
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), String buttonText = "";
decoration: BoxDecoration( bool showButton = true;
border: Border.all(color: const Color(0xFFE0E0E0)),
borderRadius: BorderRadius.circular(16), if (date.isBefore(now)) {
color: Colors.white, status = "completed";
), showButton = false;
child: Text( } else if (date.difference(now).inHours < 24) {
label, status = "in-progress";
style: fontTextStyle(13, Colors.black87, FontWeight.w500), 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<String, dynamic> delivery) {
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: const Color(0xFFE0E0E0)), border: Border.all(color: const Color(0xFFE0E0E0)),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
color: Colors.white,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -82,62 +96,60 @@ class _PlanDetailsState extends State<PlanDetails> {
padding: padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4), const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFFFF2E0), color: statusBg,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Text( child: Text(
delivery['status'], status,
style: fontTextStyle( style: fontTextStyle(12, statusText, FontWeight.w600),
12, const Color(0xFFE6882C), FontWeight.w500),
), ),
), ),
const Spacer(), const Spacer(),
Text(delivery['time'], Text(
style: fontTextStyle(12, Colors.black54, FontWeight.w400)), "${date.day}-${date.month}-${date.year}",
style:
fontTextStyle(12, Colors.black54, FontWeight.w400),
),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text("${delivery['quantity']} - ${delivery['type']}", Text(
style: fontTextStyle(14, Colors.black, FontWeight.w600)), "${widget.plan["capacity"]} - ${widget.plan["type_of_water"]}",
const SizedBox(height: 8), style: fontTextStyle(14, Colors.black, FontWeight.w600),
if (delivery.containsKey('driver')) ),
Row( const SizedBox(height: 10),
children: [ /* if (showButton)
const Icon(Icons.local_shipping_outlined, Align(
color: Color(0xFF8270DB), size: 18), alignment: Alignment.centerRight,
const SizedBox(width: 6), child: ElevatedButton(
Text( onPressed: () {},
delivery['driver'], style: ElevatedButton.styleFrom(
style: fontTextStyle(13, Colors.black87, FontWeight.w500), backgroundColor: const Color(0xFF8270DB),
) shape: RoundedRectangleBorder(
], borderRadius: BorderRadius.circular(20),
), ),
const SizedBox(height: 8), ),
Align( child: Text(
alignment: Alignment.centerRight, buttonText,
child: ElevatedButton( style:
onPressed: () {}, fontTextStyle(13, Colors.white, FontWeight.w600),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8270DB),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
), ),
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
),
child: Text(
delivery['button'],
style: fontTextStyle(13, Colors.white, FontWeight.w600),
), ),
), )*/
)
], ],
), ),
); );
} }
// ================= BUILD =================
@override @override
Widget build(BuildContext context) { 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( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
appBar: AppBar( appBar: AppBar(
@ -147,18 +159,19 @@ class _PlanDetailsState extends State<PlanDetails> {
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black), icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black),
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
), ),
title: Text("Green Valley Apartments", title: Text(
style: fontTextStyle(16, Colors.black, FontWeight.w600)), "Plan Details",
style: fontTextStyle(16, Colors.black, FontWeight.w600),
),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Image with status // IMAGE HEADER
Stack( Stack(
children: [ children: [
Image.asset( Image.asset(
'images/building.png', // replace with your asset 'images/building.png',
height: 180, height: 180,
width: double.infinity, width: double.infinity,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -167,14 +180,17 @@ class _PlanDetailsState extends State<PlanDetails> {
top: 16, top: 16,
left: 16, left: 16,
child: Container( child: Container(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 10, vertical: 4), horizontal: 10, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.green.withOpacity(0.9), color: Colors.green,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Text("Active", child: Text(
style: fontTextStyle(12, Colors.white, FontWeight.w600)), "Active",
style: fontTextStyle(
12, Colors.white, FontWeight.w600),
),
), ),
), ),
], ],
@ -182,87 +198,75 @@ class _PlanDetailsState extends State<PlanDetails> {
const SizedBox(height: 12), const SizedBox(height: 12),
// Apartment Info // PLAN SUMMARY
Padding( Column(
padding: const EdgeInsets.symmetric(horizontal: 16), children: [
child: Column( Text(plan["customerId"],
children: [ style:
Text("Green Valley Apartments", fontTextStyle(18, Colors.black, FontWeight.w600)),
style: fontTextStyle(18, Colors.black, FontWeight.w600)), const SizedBox(height: 4),
const SizedBox(height: 4), Row(
Text("Gacchibowli, Hyderabad", mainAxisAlignment: MainAxisAlignment.center,
style: fontTextStyle(14, Colors.black54, FontWeight.w400)), children: [
const SizedBox(height: 4), const Icon(Icons.water_drop,
Row( size: 16, color: Color(0xFF8270DB)),
mainAxisAlignment: MainAxisAlignment.center, const SizedBox(width: 4),
children: [ Text(plan["type_of_water"],
const Icon(Icons.water_drop, color: Color(0xFF8270DB), size: 18), style: fontTextStyle(
const SizedBox(width: 4), 14, const Color(0xFF8270DB),
Text("Drinking Water", FontWeight.w500)),
style: fontTextStyle(14, const Color(0xFF8270DB), FontWeight.w500)), ],
], ),
), const SizedBox(height: 4),
const SizedBox(height: 4), Text(
Text("25 June 2025 • 24 deliveries", "${plan["start_date"]}$totalDeliveries deliveries",
style: fontTextStyle(13, Colors.black54, FontWeight.w400)), style: fontTextStyle(
], 13, Colors.black54, FontWeight.w400),
), ),
],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Quantity & Balance // INFO ROWS
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: Row(
children: [ children: [
_buildInfoCard("10K", "Quantity"), _buildInfoCard(plan["capacity"], "Quantity"),
const SizedBox(width: 12), 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), const SizedBox(height: 16),
// Schedule | Pending | Rescheduled
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: Row(
children: [ children: [
_buildInfoCard("3/week", "Schedule"), _buildInfoCard("${plan["weekly_count"]}/week", "Schedule"),
const SizedBox(width: 12), const SizedBox(width: 12),
_buildInfoCard("14", "Pending"), _buildInfoCard("$pendingCount", "Pending"),
const SizedBox(width: 12), const SizedBox(width: 12),
_buildInfoCard("2", "Rescheduled"), _buildInfoCard("0", "Rescheduled"),
], ],
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Filter chips // DELIVERY LIST
SingleChildScrollView( ...deliveryDates.map(_buildDeliveryCard).toList(),
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(),
const SizedBox(height: 24), const SizedBox(height: 24),
// Bottom Buttons // BOTTOM BUTTONS
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: Row(
@ -271,15 +275,20 @@ class _PlanDetailsState extends State<PlanDetails> {
child: OutlinedButton( child: OutlinedButton(
onPressed: () {}, onPressed: () {},
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
side: const BorderSide(color: Color(0xFF8270DB)), side: const BorderSide(
color: Color(0xFF8270DB)),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24), 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), const SizedBox(width: 12),
@ -291,16 +300,20 @@ class _PlanDetailsState extends State<PlanDetails> {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24), 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), const SizedBox(height: 24),
], ],
), ),

@ -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<String, dynamic> 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<String> 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<String, dynamic> toJson() => {
"boreName":this.building_name,
};
}

@ -19,6 +19,9 @@ class PlanRequestsModel {
String dbId = ''; String dbId = '';
String status=''; String status='';
String frequency=''; String frequency='';
String endDate='';
String startDate='';
List dates=[];
PlanRequestsModel(); PlanRequestsModel();
@ -32,6 +35,7 @@ class PlanRequestsModel {
rtvm.capacity = json['capacity'] ?? ''; rtvm.capacity = json['capacity'] ?? '';
rtvm.quantity = json['quantity']?? ''; rtvm.quantity = json['quantity']?? '';
rtvm.frequency = json['frequency']?? ''; rtvm.frequency = json['frequency']?? '';
rtvm.dates = json['dates']?? [];
if(rtvm.frequency.toString().toLowerCase()=='weekly_twice'){ if(rtvm.frequency.toString().toLowerCase()=='weekly_twice'){
rtvm.quantity="2/Week"; rtvm.quantity="2/Week";
@ -51,6 +55,8 @@ class PlanRequestsModel {
rtvm.averageTime = json['time'] ?? ''; rtvm.averageTime = json['time'] ?? '';
rtvm.endDate = json['end_date'] ?? '';
rtvm.startDate = json['start_date'] ?? '';
rtvm.time = json['my_supplier_entry']['time'] ?? ''; rtvm.time = json['my_supplier_entry']['time'] ?? '';
rtvm.status = json['my_supplier_entry']['status'] ?? ''; rtvm.status = json['my_supplier_entry']['status'] ?? '';
rtvm.quoted_amount = json['my_supplier_entry']['quoted_amount'].toString() ?? ''; rtvm.quoted_amount = json['my_supplier_entry']['quoted_amount'].toString() ?? '';

@ -1,4 +1,5 @@
class PlansModel { class PlansModel {
final String id;
final String status; final String status;
final String apartment; final String apartment;
final String liters; final String liters;
@ -6,8 +7,10 @@ class PlansModel {
final String advance; final String advance;
final String deliveries; final String deliveries;
final String frequency; final String frequency;
final String waterType;
PlansModel({ PlansModel({
required this.id,
required this.status, required this.status,
required this.apartment, required this.apartment,
required this.liters, required this.liters,
@ -15,5 +18,22 @@ class PlansModel {
required this.advance, required this.advance,
required this.deliveries, required this.deliveries,
required this.frequency, required this.frequency,
required this.waterType,
}); });
factory PlansModel.fromJson(Map<String, dynamic> 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"],
);
}
} }

@ -61,6 +61,10 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
final _commissionCtrl = TextEditingController(); final _commissionCtrl = TextEditingController();
final _joinDateCtrl = TextEditingController(); final _joinDateCtrl = TextEditingController();
int onDeliveryCount = 0;
int availableCount = 0;
int offlineCount = 0;
// Dropdown state // Dropdown state
String? _status; // 'available' | 'on delivery' | 'offline' String? _status; // 'available' | 'on delivery' | 'offline'
final List<String> _statusOptions = const [ final List<String> _statusOptions = const [
@ -128,9 +132,31 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
final data = (jsonDecode(response)['data'] as List) final data = (jsonDecode(response)['data'] as List)
.map((e) => DriversModel.fromJson(e)) .map((e) => DriversModel.fromJson(e))
.toList(); .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; if (!mounted) return;
setState(() { setState(() {
driversList = data; driversList = data;
onDeliveryCount = onDelivery;
availableCount = available;
offlineCount = offline;
isLoading = false; isLoading = false;
}); });
} catch (e) { } catch (e) {
@ -504,16 +530,30 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: IntrinsicHeight( child: IntrinsicHeight(
child: Row( child: Row(
children: const [ children: [
Expanded( Expanded(
child: SmallMetricBox(title: 'On delivery', value: '2')), child: SmallMetricBox(
SizedBox(width: 8), title: 'On delivery',
value: onDeliveryCount.toString(),
),
),
const SizedBox(width: 8),
Expanded( Expanded(
child: SmallMetricBox(title: 'Available', value: '3')), child: SmallMetricBox(
SizedBox(width: 8), title: 'Available',
Expanded(child: SmallMetricBox(title: 'Offline', value: '1')), value: availableCount.toString(),
),
),
const SizedBox(width: 8),
Expanded(
child: SmallMetricBox(
title: 'Offline',
value: offlineCount.toString(),
),
),
], ],
), ),
), ),
), ),

@ -75,6 +75,9 @@ class _ResourcesFleetScreenState extends State<ResourcesFleetScreen> {
String search = ''; String search = '';
bool isLoading = false; bool isLoading = false;
List<TankersModel> tankersList = []; List<TankersModel> tankersList = [];
int activeCount = 0;
int inactiveCount = 0;
int maintenanceCount = 0;
@override @override
void initState() { void initState() {
@ -99,9 +102,29 @@ class _ResourcesFleetScreenState extends State<ResourcesFleetScreen> {
final data = (jsonDecode(response)['data'] as List) final data = (jsonDecode(response)['data'] as List)
.map((e) => TankersModel.fromJson(e)) .map((e) => TankersModel.fromJson(e))
.toList(); .toList();
int active = 0;
int inactive = 0;
int maintenance = 0;
for (final t in data) {
final statuses = List<String>.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; if (!mounted) return;
setState(() { setState(() {
tankersList = data; tankersList = data;
activeCount = active;
inactiveCount = inactive;
maintenanceCount = maintenance;
isLoading = false; isLoading = false;
}); });
} catch (e) { } catch (e) {
@ -475,12 +498,12 @@ class _ResourcesFleetScreenState extends State<ResourcesFleetScreen> {
IntrinsicHeight( IntrinsicHeight(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [ children: [
Expanded(child: SmallMetricBox(title: 'Active', value: '2')), Expanded(child: SmallMetricBox(title: 'Active', value: activeCount.toString())),
SizedBox(width: 8), const SizedBox(width: 8),
Expanded(child: SmallMetricBox(title: 'Inactive', value: '3')), Expanded(child: SmallMetricBox(title: 'Inactive', value: inactiveCount.toString())),
SizedBox(width: 8), const SizedBox(width: 8),
Expanded(child: SmallMetricBox(title: 'Under Maintenance', value: '1')), Expanded(child: SmallMetricBox(title: 'Under Maintenance', value: maintenanceCount.toString())),
], ],
), ),
), ),

@ -19,7 +19,12 @@ class _ResourcesMainScreenState extends State<ResourcesMainScreen>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_tabController = TabController(length: _labels.length, vsync: this); _tabController = TabController(
length: _labels.length,
vsync: this,
initialIndex: AppSettings.resourcesInitialTab,
);
AppSettings.resourcesInitialTab = 0;
} }
@override @override

@ -102,6 +102,9 @@ class _ResourcesSourceScreenState extends State<ResourcesSourceScreen> {
double? lat; double? lat;
double? lng; double? lng;
String? address; String? address;
int drinkingCount = 0;
int boreCount = 0;
int bothCount = 0;
String? _required(String? v, {String field = "This field"}) { String? _required(String? v, {String field = "This field"}) {
if (v == null || v.trim().isEmpty) return "$field is required"; if (v == null || v.trim().isEmpty) return "$field is required";
@ -143,9 +146,31 @@ class _ResourcesSourceScreenState extends State<ResourcesSourceScreen> {
final data = (jsonDecode(response)['data'] as List) final data = (jsonDecode(response)['data'] as List)
.map((e) => SourceLocationsModel.fromJson(e)) .map((e) => SourceLocationsModel.fromJson(e))
.toList(); .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; if (!mounted) return;
setState(() { setState(() {
sourceLocationsList = data; sourceLocationsList = data;
drinkingCount = drinking;
boreCount = bore;
bothCount = both;
isLoading = false; isLoading = false;
}); });
} catch (e) { } catch (e) {
@ -536,14 +561,30 @@ class _ResourcesSourceScreenState extends State<ResourcesSourceScreen> {
IntrinsicHeight( IntrinsicHeight(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [ children: [
Expanded(child: SmallMetricBox(title: 'Drinking water', value: '2')), Expanded(
SizedBox(width: 8), child: SmallMetricBox(
Expanded(child: SmallMetricBox(title: 'Bore water', value: '3')), title: 'Drinking water',
SizedBox(width: 8), value: drinkingCount.toString(),
Expanded(child: SmallMetricBox(title: 'Both', value: '1')), ),
),
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<ResourcesSourceScreen> {
Expanded( Expanded(
child: isLoading child: isLoading
? const Center(child: CircularProgressIndicator()) ? 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( : ListView.separated(
itemCount: filtered.length, itemCount: filtered.length,
separatorBuilder: (_, __) => const SizedBox(height: 10), separatorBuilder: (_, __) => const SizedBox(height: 10),
@ -622,7 +677,7 @@ class _ResourcesSourceScreenState extends State<ResourcesSourceScreen> {
), ),
); );
}, },
), )),
), ),
], ],
), ),

@ -944,6 +944,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -989,6 +997,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" 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: term_glyph:
dependency: transitive dependency: transitive
description: description:

@ -33,6 +33,7 @@ dependencies:
provider: ^6.0.5 provider: ^6.0.5
google_maps_place_picker_mb: ^3.0.2 google_maps_place_picker_mb: ^3.0.2
dropdown_button2: ^2.0.0 dropdown_button2: ^2.0.0
table_calendar: ^3.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

Loading…
Cancel
Save