master
Sneha 2 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:google_fonts/google_fonts.dart';
import 'package:intl/intl.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 {
const DeliveryCalendarScreen({super.key});
class SupplyCalendarScreen extends StatefulWidget {
@override
State<DeliveryCalendarScreen> createState() => _DeliveryCalendarScreenState();
State<SupplyCalendarScreen> createState() => _SupplyCalendarScreenState();
}
class _DeliveryCalendarScreenState extends State<DeliveryCalendarScreen> {
DateTime _focusedMonth = DateTime(2025, 10);
late List<Map<String, dynamic>> _calendarData;
class _SupplyCalendarScreenState extends State<SupplyCalendarScreen> {
late DateTime _focusedDay;
late DateTime _firstDay;
late DateTime _lastDay;
Map<DateTime, List<Map<String, dynamic>>> calendarEvents = {};
DateTime? _selectedDay;
bool isLoading = true;
@override
void initState() {
super.initState();
_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() {
return [
{"day": 1, "status": "Delivered"},
{"day": 2, "status": "Delivered"},
{"day": 3, "status": "Rescheduled"},
{"day": 4, "status": "Delivered"},
{"day": 5, "status": "Delivered"},
{"day": 6, "status": "Delivered"},
{"day": 7, "status": "Cancelled"},
{"day": 8, "status": "Delivered"},
{"day": 9, "status": "Delivered"},
{"day": 10, "status": "Delivered"},
{"day": 11, "status": "Cancelled"},
{"day": 12, "status": "Delivery"},
{"day": 13, "status": "Delivery"},
{"day": 14, "status": "Delivery"},
{"day": 15, "status": "Delivery"},
{"day": 16, "status": "Delivery"},
{"day": 17, "status": "Delivery"},
{"day": 18, "status": "Delivery"},
{"day": 19, "status": "Delivery"},
{"day": 20, "status": "Delivery"},
{"day": 21, "status": "Delivery"},
{"day": 22, "status": "Delivery"},
{"day": 23, "status": "Delivery"},
{"day": 24, "status": "Delivery"},
{"day": 25, "status": "Delivery"},
{"day": 26, "status": "Delivery"},
];
void _confirmCancel(String orderId) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text("Cancel Delivery"),
content: Text("Are you sure you want to cancel this delivery?"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context), child: Text("No")),
TextButton(
onPressed: () {
Navigator.pop(context);
cancelDelivery(orderId);
},
child: Text("Yes, Cancel", style: TextStyle(color: Colors.red))),
],
),
);
}
Color _getBackgroundColor(String status) {
switch (status) {
case "Delivered":
return const Color(0xFFE6F4EA);
case "Cancelled":
return const Color(0xFFFDE8E8);
case "Rescheduled":
return const Color(0xFFF2F2F2);
case "Delivery":
return const Color(0xFFEFF4FF);
default:
return Colors.white;
// ===========================================================================
// HELPER FUNCTIONS
// ===========================================================================
String formatShort(String date) {
try {
final d = DateTime.parse(date);
return "${d.day} ${monthShort[d.month - 1]}";
} catch (e) {
return date;
}
}
Color _getTextColor(String status) {
switch (status) {
case "Delivered":
return Colors.green;
case "Cancelled":
return Colors.red;
case "Rescheduled":
return Colors.black54;
case "Delivery":
return const Color(0xFF3B6FE0);
default:
return Colors.black87;
List<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);
}
Widget _getStatusIcon(String status) {
switch (status) {
case "Delivered":
return const Icon(Icons.check, size: 16, color: Colors.green);
case "Cancelled":
return const Icon(Icons.close, size: 16, color: Colors.red);
case "Rescheduled":
return const Icon(Icons.access_time, size: 16, color: Colors.black54);
case "Delivery":
return const Icon(Icons.local_shipping, size: 16, color: Color(0xFF3B6FE0));
default:
return const SizedBox.shrink();
String _buildStatusText(Map<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;
}
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);
final firstDayNextMonth = DateTime(date.year, date.month + 1, 1);
return firstDayNextMonth.difference(firstDayThisMonth).inDays;
// ===========================================================================
// 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) {
final int totalDays = _daysInMonth(_focusedMonth);
final int firstWeekday = DateTime(_focusedMonth.year, _focusedMonth.month, 1).weekday;
final int totalSlots = totalDays + (firstWeekday - 1);
if (isLoading) {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
final height = MediaQuery.of(context).size.height;
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black),
onPressed: () => Navigator.pop(context),
),
title: Text("Calendar", style: fontTextStyle(16, Colors.black, FontWeight.w600)),
actions: const [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
child: Icon(Icons.calendar_month_outlined, color: Color(0xFF8270DB)),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
child: Icon(Icons.notifications_none_rounded, color: Colors.black87),
),
],
),
body: Column(
children: [
const SizedBox(height: 8),
Text(_getMonthYear(), style: fontTextStyle(16, Colors.black, FontWeight.w600)),
const SizedBox(height: 8),
// Weekdays Row
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
children: const [
Expanded(child: Center(child: Text("MON"))),
Expanded(child: Center(child: Text("TUE"))),
Expanded(child: Center(child: Text("WED"))),
Expanded(child: Center(child: Text("THU"))),
Expanded(child: Center(child: Text("FRI"))),
Expanded(child: Center(child: Text("SAT"))),
Expanded(child: Center(child: Text("SUN"))),
],
),
SizedBox(height: 50),
Text(
"${monthShort[_focusedDay.month - 1]} ${_focusedDay.year}",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
// Calendar Grid
Expanded(
child: GridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 4),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7,
crossAxisSpacing: 4,
mainAxisSpacing: 4,
),
itemCount: totalSlots,
itemBuilder: (context, index) {
if (index < firstWeekday - 1) {
return const SizedBox.shrink();
}
final day = index - (firstWeekday - 2);
final status = _calendarData
.firstWhere(
(item) => item['day'] == day,
orElse: () => {"status": ""},
)['status']
.toString();
return Container(
decoration: BoxDecoration(
color: _getBackgroundColor(status),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"$day",
style: fontTextStyle(13, _getTextColor(status), FontWeight.w600),
),
const SizedBox(height: 4),
_getStatusIcon(status),
],
),
);
child: TableCalendar(
firstDay: _firstDay,
lastDay: _lastDay,
focusedDay: _focusedDay,
headerVisible: false,
calendarFormat: CalendarFormat.month,
rowHeight: (height - 200) / 6,
daysOfWeekHeight: 40,
onPageChanged: (focused) {
setState(() => _focusedDay = focused);
},
onDaySelected: (selected, focused) {
_focusedDay = focused;
_selectedDay = selected;
showDeliveryList(selected);
setState(() {});
},
calendarBuilders: CalendarBuilders(
defaultBuilder: (context, date, _) {
final key = DateTime(date.year, date.month, date.day);
final events = calendarEvents[key];
Color bg = Colors.transparent;
if (events != null && events.isNotEmpty) {
String s = events.first["status"];
if (s == "rescheduled_to")
bg = Colors.deepOrange.withOpacity(0.10);
else if (s == "rescheduled_from")
bg = Colors.orange.withOpacity(0.10);
else if (s == "cancelled")
bg = Colors.red.withOpacity(0.10);
else
bg = Colors.blue.withOpacity(0.08);
}
// Grouping counts (delivery, cancelled, rescheduled)
Map<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/search_order_appbar.dart';
import 'package:supplier_new/plans/all_plans.dart';
import 'package:supplier_new/plans/calendar.dart';
import 'package:supplier_new/plans/plan_requests.dart';
import 'package:supplier_new/plans/search_plan_appbar.dart';
import 'package:supplier_new/set_rates/set_rates.dart';
import '../login/login.dart';
import '../profile/fleet.dart';
import '../resources/drivers_model.dart';
import '../resources/resources_fleet.dart';
import '../resources/resources_main.dart';
import '../resources/tankers_model.dart';
import 'calander.dart';
class DashboardScreen extends StatefulWidget {
@ -323,7 +326,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DeliveryCalendarScreen(),
builder: (context) => SupplierCalendar(),
),
);
},
@ -763,6 +766,82 @@ class HomeScreen extends StatefulWidget {
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
Widget build(BuildContext context) {
return SingleChildScrollView(
@ -851,18 +930,61 @@ class _HomeScreenState extends State<HomeScreen> {
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 1.5,
children: const [
DashboardCard(
title: "Active Vehicles",
value: "12/14",
subtitle: "2 in maintenance",
icon: Icons.local_shipping_outlined,
children: [
GestureDetector(
onTap: () async {
// 👉 Switch to Resources tab (index = 3)
final dashboardState =
context.findAncestorStateOfType<_DashboardScreenState>();
dashboardState?.setState(() {
dashboardState._currentIndex = 3;
});
// 👉 Wait for user to come back to Home
await Future.delayed(const Duration(milliseconds: 300));
// 👉 Refresh counts when back
_fetchTankerCounts();
_fetchDriverCounts();
},
child: DashboardCard(
title: "Active Vehicles",
value:
"${AppSettings.tankerActiveCount}/${AppSettings.totalTankers}",
subtitle:
"${AppSettings.tankerMaintenanceCount} in maintenance",
icon: Icons.local_shipping_outlined,
),
),
DashboardCard(
title: "Available Drivers",
value: "2/10",
subtitle: "6 on delivery, 2 offline",
icon: Icons.person_outline,
GestureDetector(
onTap: () async {
// 👉 Tell Resources to open Drivers tab
AppSettings.resourcesInitialTab = 1; // DRIVERS
// 👉 Switch to Resources screen
final dashboardState =
context.findAncestorStateOfType<_DashboardScreenState>();
dashboardState?.setState(() {
dashboardState._currentIndex = 3;
});
// 👉 When user comes back to dashboard
await Future.delayed(const Duration(milliseconds: 300));
_fetchDriverCounts();
_fetchTankerCounts();
},
child: DashboardCard(
title: "Available Drivers",
value:
"${AppSettings.driverAvailableCount}/${AppSettings.totalDrivers}",
subtitle:
"${AppSettings.driverOnDeliveryCount} on delivery, "
"${AppSettings.driverOfflineCount} offline",
icon: Icons.person_outline,
),
),
],
),

@ -158,6 +158,7 @@ class AppSettings{
static String getAcceptedOrdersFromUsersUrl = host + 'getAllTankersBookingdetails';
static String getPlanRequestsFromUsersUrl = host + 'getuserRequestbookingsforplansforsupplier';
static String acceptPlanRequestsUrl = host + 'supplier/recurring/respond';
static String acceptPlanRequestsUrl1 = host + 'recurringRequestedBooking';
static String getTankersUrl = host + 'getTankers';
static String getTankerDetailsByNameUrl = host + 'getsingledetails';
static String addTankerUrl = host + 'addTankers';
@ -179,10 +180,30 @@ class AppSettings{
static String updatePumpFeeUrl = host + 'suppliers';
static String assignTankerUrl = host + 'assign-deliveryboy-tanker';
static String getDriverDetailsByPhoneUrl = host + 'getsingledeliveryboydetails';
static String getAcceptedRecurringBookingsUrl = host + 'supplier/recurring';
static String getAllTransactionsUrl = host + 'supplierAccounts';
static String getAdvanceTransactionsBySupplierUrl = host + 'advance/transactions/supplier';
static String createAdvanceRequestUrl = host + 'advance/request';
static String acceptAdvanceFromCustomerUrl = host + 'advance/confirm';
static String getAdvanceTransactionsBySupplierAndCustomerUrl = host + 'advance/transactions';
static String respondRecurringBookingUrl = host + 'customer/recurring/respond';
static String getSupplierBookingsUrl = host + 'getsupplierbookings';
static int driverAvailableCount = 0;
static int driverOnDeliveryCount = 0;
static int driverOfflineCount = 0;
static int totalDrivers = 0;
static int tankerActiveCount = 0;
static int tankerMaintenanceCount = 0;
static int totalTankers = 0;
static int resourcesInitialTab = 0;
static List<String> existingCreditCustomerIds = [];
static String formDouble(dynamic s) {
var comma = NumberFormat('#,##,###.##', 'en_IN');
@ -504,15 +525,15 @@ class AppSettings{
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) {
return true;
} else if (response.statusCode == 401) {
bool status = await AppSettings.resetToken();
if (status) {
response = await http.put(uri,body: json.encode(payload), headers: await buildRequestHeaders());
response = await http.post(uri,body: json.encode(payload), headers: await buildRequestHeaders());
if (response.statusCode == 200) {
return true;
} else {
@ -1175,6 +1196,234 @@ class AppSettings{
}
}
static Future<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*/

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

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

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

@ -15,12 +15,160 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
//int advance =0;
double amountToPayAfterDelivery = 0.0;
String paymentMode = "after_delivery"; // advance | credit | after_delivery
TextEditingController advanceAmountCtrl = TextEditingController();
TextEditingController creditLimitCtrl = TextEditingController();
final TextEditingController tankerPriceController = TextEditingController();
final List<Map<String, String>> paymentTypes = [
{"key": "advance", "label": "Advance"},
{"key": "after_delivery", "label": "After Delivery"},
{"key": "credit", "label": "Credit"},
];
String? advanceError;
String? creditError;
@override
void initState() {
// TODO: implement initState
tankerPriceController.text = '${widget.order.quoted_amount}';
super.initState();
}
Future<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
Widget build(BuildContext context) {
return Scaffold(
@ -177,13 +325,74 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
SizedBox(
height: MediaQuery.of(context).size.height * .011,
),
_detailTwoRow(
_twoFields(tankerPriceController, "Tanker Price",
'images/price.png', false, null, null, null, null),
Container(
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
color: const Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0XFFC3C4C4)),
),
child: Padding(
padding: const EdgeInsets.all(4),
child: GestureDetector(
onTap: () => showAdvanceSelectionBottomSheet(context),
behavior: HitTestBehavior.opaque,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// LEFT CONTENT
Expanded(
child: Stack(
alignment: Alignment.centerRight,
children: [
_detailTwoRow(
"Payment Type",
paymentMode == "advance"
? "Advance - ₹${advanceAmountCtrl.text.isEmpty ? "0" : advanceAmountCtrl.text}"
: paymentMode == "credit"
? "Credit - ₹${creditLimitCtrl.text.isEmpty ? "0" : creditLimitCtrl.text}"
: "After Delivery",
"images/advance.png",
"",
"",
"",
),
// Arrow FIXED near text
Padding(
padding: const EdgeInsets.only(right: 8),
child: Image.asset(
'images/downarrow.png',
height: 18,
width: 18,
color: const Color(0XFFC3C4C4),
),
),
],
),
),
// Edit icon at far right
Image.asset(
'images/edit.png',
height: 18,
width: 18,
color: const Color(0XFFC3C4C4),
),
],
),
),
),
),
/* _detailTwoRow(
"Tanker Price",
"${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}",
"images/financialsBottomIcon.png",
"",
"",
""),
""),*/
SizedBox(
height: MediaQuery.of(context).size.height * .02,
),
@ -194,41 +403,30 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
"Capacity",
"${widget.order.capacity}",
"images/capacity.png",
),
SizedBox(
height: MediaQuery.of(context).size.height * .02,
),
_detailTwoRow(
"Start Date",
"${widget.order.averageTime}",
"${widget.order.startDate}",
"images/time.png",
"End Date",
"${widget.order.averageTime}",
"images/advance.png",
"${widget.order.endDate}",
"images/time.png",
),
SizedBox(
height: MediaQuery.of(context).size.height * .02,
),
_detailTwoRow(
"Frequency",
"${widget.order.quantity}",
"images/quantity.png",
"",
"",
""),
_detailTwoRow("Frequency", "${widget.order.quantity}",
"images/quantity.png", "", "", ""),
],
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .008,
),
/// 🔹 Additional Details
Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
padding: EdgeInsets.fromLTRB(16, 16, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -240,14 +438,8 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
SizedBox(
height: MediaQuery.of(context).size.height * .011,
),
Text(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut "
"aliquip ex ea commodo consequat.",
style: fontTextStyle(
12, const Color(0XFF646566), FontWeight.w400),
),
Text('Total tankers for this plan are ${widget.order.dates.length}', style: fontTextStyle(
12, const Color(0xFF939495), FontWeight.w400),)
],
)),
@ -265,9 +457,31 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
SizedBox(
height: MediaQuery.of(context).size.height * .011,
),
_detailRow("Tanker Price",
"${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}"),
/* SizedBox(
ValueListenableBuilder<TextEditingValue>(
valueListenable: tankerPriceController,
builder: (context, value, _) {
final int tankerCount = widget.order.dates.length;
final int price =
int.tryParse(value.text.isEmpty ? "0" : value.text) ?? 0;
return Text.rich(
TextSpan(
children: [
TextSpan(
text: "Total amount for all plan orders ",
style: fontTextStyle(
12, const Color(0xFF939495), FontWeight.w400)),
TextSpan(
text: '$tankerCount × $price = ${tankerCount * price}',
style: fontTextStyle(
12, const Color(0xFF515253), FontWeight.w500)),
],
),
textAlign: TextAlign.center,
);
},
),
/* SizedBox(
height: MediaQuery.of(context).size.height * .004,
),
@ -310,14 +524,13 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
Expanded(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: Color(0XFF000000),
backgroundColor: Color(0xFFF1F1F1),
side: BorderSide(color: Color(0xFFF1F1F1)),
padding:
EdgeInsets.symmetric(vertical: 10), // uniform height
foregroundColor: Color(0XFFE2483D),
backgroundColor: Colors.white,
side: BorderSide(color: Color(0XFFE2483D)),
padding: EdgeInsets.symmetric(vertical: 10),
),
onPressed: () {
Navigator.push(
/*Navigator.push(
context,
MaterialPageRoute(
builder: (_) => EditPlanRequests(
@ -325,15 +538,16 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
status: widget.status,
),
),
);
);*/
Navigator.pop(context);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('images/edit.png', height: 20, width: 20),
Image.asset('images/cross.png', height: 20, width: 20),
SizedBox(width: 4),
Text(
"Edit Plan",
"Cancel",
style: fontTextStyle(
14, const Color(0XFF000000), FontWeight.w400),
),
@ -345,12 +559,12 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
Expanded(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: Color(0XFFE2483D),
backgroundColor: Colors.white,
foregroundColor: Color(0XFFFFFFFF),
backgroundColor: Color(0XFFE2483D),
side: BorderSide(color: Color(0XFFE2483D)),
padding: EdgeInsets.symmetric(vertical: 10),
),
onPressed: ()async {
onPressed: () async {
/*AppSettings.preLoaderDialog(context);
bool isOnline = await AppSettings.internetConnectivity();
@ -390,12 +604,12 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('images/cross.png', height: 20, width: 20),
Image.asset('images/cross.png', height: 20, width: 20,color: Color(0XFFFFFFFF),),
SizedBox(width: 4),
Text(
"Reject",
style: fontTextStyle(
14, const Color(0XFF000000), FontWeight.w400),
14, const Color(0XFFFFFFFF), FontWeight.w400),
),
],
),
@ -414,33 +628,57 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
bool isOnline = await AppSettings.internetConnectivity();
if (isOnline) {
var payload = new Map<String, dynamic>();
/*payload["supplierId"] = AppSettings.supplierId;
payload["amount"] = int.parse(widget.order.quoted_amount);
payload["delivery_charges"] = advance;*/
payload["action"] = 'accept';
if (!isOnline) {
Navigator.of(context, rootNavigator: true).pop();
AppSettings.longFailedToast("Please Check internet");
return;
}
if (!isPaymentValid()) {
Navigator.of(context, rootNavigator: true).pop();
AppSettings.longFailedToast(
paymentMode == "advance"
? "Please enter advance amount"
: "Please enter credit limit",
);
return;
}
try {
final Map<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(
payload, widget.order.dbId);
payload,
widget.order.dbId,
);
try {
if (status) {
Navigator.of(context,rootNavigator: true).pop();
Navigator.pop(context, true);
}
else{
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Accept of plan request Failed");
}
} catch (e) {
Navigator.of(context,rootNavigator: true).pop();
print(e);
Navigator.of(context, rootNavigator: true).pop();
if (status) {
Navigator.pop(context, true);
} else {
AppSettings.longFailedToast("Accept of plan request Failed");
}
}
else{
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Please Check internet");
} catch (e) {
Navigator.of(context, rootNavigator: true).pop();
AppSettings.longFailedToast("Something went wrong");
}
},
child: Row(
@ -462,6 +700,107 @@ class _AcceptPlanRequestsState extends State<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
Widget _detailRow(String title, String value) {
return Padding(
@ -587,17 +926,19 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
const SizedBox(height: 20),
Text(
"REASON FOR REJECTION",
style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600),
style: fontTextStyle(
10, const Color(0XFF2D2E30), FontWeight.w600),
),
const SizedBox(height: 8),
// reasons
...reasons.map(
(reason) => RadioListTile<String>(
(reason) => RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(
reason,
style: fontTextStyle(14, const Color(0XFF2D2E30), FontWeight.w400),
style: fontTextStyle(
14, const Color(0XFF2D2E30), FontWeight.w400),
),
value: reason,
activeColor: const Color(0XFFE2483D),
@ -614,14 +955,15 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
onPressed: () => Navigator.pop(context, null),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
side: const BorderSide(color:Color(0XFFFFFFFF)),
side: const BorderSide(color: Color(0XFFFFFFFF)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
),
child: Text(
"Cancel",
style: fontTextStyle(14, const Color(0XFF939495), FontWeight.w600),
style: fontTextStyle(
14, const Color(0XFF939495), FontWeight.w600),
),
),
),
@ -641,7 +983,8 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
),
child: Text(
"Reject Request",
style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600),
style: fontTextStyle(
14, const Color(0XFFFFFFFF), FontWeight.w600),
),
),
),
@ -656,5 +999,4 @@ class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
},
);
}
}

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

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

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

@ -1,4 +1,5 @@
class PlansModel {
final String id;
final String status;
final String apartment;
final String liters;
@ -6,8 +7,10 @@ class PlansModel {
final String advance;
final String deliveries;
final String frequency;
final String waterType;
PlansModel({
required this.id,
required this.status,
required this.apartment,
required this.liters,
@ -15,5 +18,22 @@ class PlansModel {
required this.advance,
required this.deliveries,
required this.frequency,
required this.waterType,
});
factory PlansModel.fromJson(Map<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 _joinDateCtrl = TextEditingController();
int onDeliveryCount = 0;
int availableCount = 0;
int offlineCount = 0;
// Dropdown state
String? _status; // 'available' | 'on delivery' | 'offline'
final List<String> _statusOptions = const [
@ -128,9 +132,31 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
final data = (jsonDecode(response)['data'] as List)
.map((e) => DriversModel.fromJson(e))
.toList();
int onDelivery = 0;
int available = 0;
int offline = 0;
for (final d in data) {
switch (d.status) {
case 'on delivery':
onDelivery++;
break;
case 'available':
available++;
break;
case 'offline':
offline++;
break;
}
}
if (!mounted) return;
setState(() {
driversList = data;
onDeliveryCount = onDelivery;
availableCount = available;
offlineCount = offline;
isLoading = false;
});
} catch (e) {
@ -504,16 +530,30 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
padding: const EdgeInsets.symmetric(horizontal: 16),
child: IntrinsicHeight(
child: Row(
children: const [
children: [
Expanded(
child: SmallMetricBox(title: 'On delivery', value: '2')),
SizedBox(width: 8),
child: SmallMetricBox(
title: 'On delivery',
value: onDeliveryCount.toString(),
),
),
const SizedBox(width: 8),
Expanded(
child: SmallMetricBox(title: 'Available', value: '3')),
SizedBox(width: 8),
Expanded(child: SmallMetricBox(title: 'Offline', value: '1')),
child: SmallMetricBox(
title: 'Available',
value: availableCount.toString(),
),
),
const SizedBox(width: 8),
Expanded(
child: SmallMetricBox(
title: 'Offline',
value: offlineCount.toString(),
),
),
],
),
),
),

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

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

@ -102,6 +102,9 @@ class _ResourcesSourceScreenState extends State<ResourcesSourceScreen> {
double? lat;
double? lng;
String? address;
int drinkingCount = 0;
int boreCount = 0;
int bothCount = 0;
String? _required(String? v, {String field = "This field"}) {
if (v == null || v.trim().isEmpty) return "$field is required";
@ -143,9 +146,31 @@ class _ResourcesSourceScreenState extends State<ResourcesSourceScreen> {
final data = (jsonDecode(response)['data'] as List)
.map((e) => SourceLocationsModel.fromJson(e))
.toList();
int drinking = 0;
int bore = 0;
int both = 0;
for (final s in data) {
switch ((s.water_type ?? '').toLowerCase()) {
case 'drinking water':
drinking++;
break;
case 'bore water':
bore++;
break;
case 'both':
both++;
break;
}
}
if (!mounted) return;
setState(() {
sourceLocationsList = data;
drinkingCount = drinking;
boreCount = bore;
bothCount = both;
isLoading = false;
});
} catch (e) {
@ -536,14 +561,30 @@ class _ResourcesSourceScreenState extends State<ResourcesSourceScreen> {
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
Expanded(child: SmallMetricBox(title: 'Drinking water', value: '2')),
SizedBox(width: 8),
Expanded(child: SmallMetricBox(title: 'Bore water', value: '3')),
SizedBox(width: 8),
Expanded(child: SmallMetricBox(title: 'Both', value: '1')),
children: [
Expanded(
child: SmallMetricBox(
title: 'Drinking water',
value: drinkingCount.toString(),
),
),
const SizedBox(width: 8),
Expanded(
child: SmallMetricBox(
title: 'Bore water',
value: boreCount.toString(),
),
),
const SizedBox(width: 8),
Expanded(
child: SmallMetricBox(
title: 'Both',
value: bothCount.toString(),
),
),
],
),
),
],
),
@ -596,6 +637,20 @@ class _ResourcesSourceScreenState extends State<ResourcesSourceScreen> {
Expanded(
child: isLoading
? const Center(child: CircularProgressIndicator())
: (filtered.isEmpty
? Center(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12),
child: Text(
'No Data Available',
style: fontTextStyle(
12,
const Color(0xFF939495),
FontWeight.w500),
),
),
)
: ListView.separated(
itemCount: filtered.length,
separatorBuilder: (_, __) => const SizedBox(height: 10),
@ -622,7 +677,7 @@ class _ResourcesSourceScreenState extends State<ResourcesSourceScreen> {
),
);
},
),
)),
),
],
),

@ -944,6 +944,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.1"
simple_gesture_detector:
dependency: transitive
description:
name: simple_gesture_detector
sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
url: "https://pub.dev"
source: hosted
version: "0.2.1"
sky_engine:
dependency: transitive
description: flutter
@ -989,6 +997,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
table_calendar:
dependency: "direct main"
description:
name: table_calendar
sha256: "7f1270313c0cdb245b583ed8518982c01d4a7e95869b3c30abcbae3b642c45d0"
url: "https://pub.dev"
source: hosted
version: "3.0.8"
term_glyph:
dependency: transitive
description:

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

Loading…
Cancel
Save