plans page changes

master
Sneha 2 months ago
parent 20fcf1e9af
commit dfc47308d6

@ -934,7 +934,7 @@ class _HomeScreenState extends State<HomeScreen> {
Navigator.push(
context,
new MaterialPageRoute(
builder: (__) => new PlanRequestPage()));
builder: (__) => new PlanRequestsPage()));
},
child: RequestCard(
title: "Plan Requests",

@ -154,6 +154,7 @@ class AppSettings{
static String getOrderRequestsFromUsersUrl = host + 'getuserRequestbookingsforsupplier';
static String acceptOrderRequestsUrl = host + 'request-booking-with-charges';
static String getAcceptedOrdersFromUsersUrl = host + 'supplier/booking/advance-paid';
static String getPlanRequestsFromUsersUrl = host + 'getuserRequestbookingsforplansforsupplier';
static String getTankersUrl = host + 'getTankers';
static String addTankerUrl = host + 'addTankers';
static String getDriversUrl = host + 'getalldeliveryboys';
@ -460,6 +461,30 @@ class AppSettings{
}
}
static Future<String> getPlanRequestsFromUsers() async {
var uri = Uri.parse(getPlanRequestsFromUsersUrl+'/'+supplierId);
//uri = uri.replace(query: 'customerId=$customerId');
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> getTankers() async {
var uri = Uri.parse(getTankersUrl);
uri = uri.replace(query: 'supplierId=$supplierId');

@ -1,10 +1,8 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/orders/edit_order_requests.dart';
import '../resources/drivers_model.dart';
import '../resources/tankers_model.dart';
class AssignDriverScreen extends StatefulWidget {
var order;
@ -20,12 +18,15 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
double amountToPayAfterDelivery = 0.0;
double totalFare = 0.0;
bool isLoading = false;
bool isTankersDataLoading = false;
List<DriversModel> driversList = [];
List<TankersModel> tankersList = [];
@override
void initState() {
// TODO: implement initState
super.initState();
_fetchTankers();
_fetchDrivers();
advance = 150;
advancePayable = advance;
@ -51,6 +52,26 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
}
}
Future<void> _fetchTankers() async {
setState(() => isTankersDataLoading = true);
try {
final response = await AppSettings.getTankers();
final data = (jsonDecode(response)['data'] as List)
.map((e) => TankersModel.fromJson(e))
.toList();
if (!mounted) return;
setState(() {
tankersList = data;
isTankersDataLoading = false;
});
} catch (e) {
debugPrint("⚠️ Error fetching tankers: $e");
setState(() => isTankersDataLoading = false);
}
}
void _showAssignDriverBottomSheet() {
int? selectedDriverIndex; // Track selected driver
@ -293,9 +314,243 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
);
}
void _showAssignTankerBottomSheet() {
int? selectedTankerIndex; // Track selected tanker
// 🔹 Helper function to normalize capacity values
int _capToLiters(dynamic cap) {
if (cap == null) return -1;
if (cap is num) return cap.round();
final s = cap.toString().toLowerCase().replaceAll(',', '').trim();
final match = RegExp(r'(\d+(\.\d+)?)').firstMatch(s);
if (match == null) return -1;
final n = double.tryParse(match.group(1)!) ?? -1;
if (n < 0) return -1;
// If string contains "kl" -> convert KL to Liters
if (s.contains('kl')) return (n * 1000).round();
// Otherwise already in liters
return n.round();
}
showModalBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: false,
enableDrag: false,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return WillPopScope(
onWillPop: () async => false,
child: StatefulBuilder(
builder: (context, setModalState) {
return DraggableScrollableSheet(
expand: false,
initialChildSize: 0.7,
minChildSize: 0.5,
maxChildSize: 0.9,
builder: (context, scrollController) {
final requiredLiters = _capToLiters(widget.order.capacity);
final filteredTankers = (tankersList ?? [])
.where((t) => _capToLiters(t.capacity) == requiredLiters)
.toList();
return Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 60,
height: 4,
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: const Color(0xFFE0E0E0),
borderRadius: BorderRadius.circular(2),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Assign Tanker",
style: fontTextStyle(16, const Color(0XFF2A2A2A), FontWeight.w600),
),
GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Image.asset(
'images/cross.png',
height: 24,
width: 24,
color: const Color(0XFF2A2A2A),
),
)
],
),
const SizedBox(height: 16),
// 🏢 Order Info
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
border: Border.all(color: const Color(0XFFC9C2F0), width: 0.5),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.order.building_name,
style: fontTextStyle(20, const Color(0XFF2D2E30), FontWeight.w600),
),
Text(
widget.order.displayAddress,
style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400),
),
],
),
const Spacer(),
Text(
'${widget.order.distanceInKm} Km',
style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400),
),
],
),
),
const SizedBox(height: 16),
Text(
"SELECT TANKER",
style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600),
),
const SizedBox(height: 12),
// 🧍 Tanker List
Expanded(
child: isTankersDataLoading
? const Center(child: CircularProgressIndicator())
: filteredTankers.isEmpty
? Center(
child: Text(
'No data available for capacity ${widget.order.capacity}',
style: fontTextStyle(12, const Color(0xFF939495), FontWeight.w500),
),
)
: ListView.separated(
controller: scrollController,
itemCount: filteredTankers.length,
separatorBuilder: (_, __) => const SizedBox(height: 12),
itemBuilder: (context, idx) {
final d = filteredTankers[idx];
final bool isSelected = selectedTankerIndex == idx;
return GestureDetector(
onTap: () {
setModalState(() {
selectedTankerIndex = idx;
});
},
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: isSelected
? const Color(0XFF8270DB)
: const Color(0XFFC3C4C4),
width: 1,
),
),
color: isSelected
? const Color(0XFFEDEBFF)
: Colors.white,
child: Padding(
padding: const EdgeInsets.all(0.0),
child: TankersCard(
title: d.tanker_name,
subtitle: d.type_of_water,
capacity: d.capacity,
code: d.license_plate,
owner: d.supplier_name,
status: List<String>.from(d.availability),
),
),
),
);
},
),
),
const SizedBox(height: 16),
// 🟣 Assign Button
SizedBox(
width: double.infinity,
child: GestureDetector(
onTap: () async {
if (selectedTankerIndex == null) {
AppSettings.longFailedToast('Please select tanker');
return;
}
if (filteredTankers.isEmpty ||
selectedTankerIndex! >= filteredTankers.length) {
AppSettings.longFailedToast('Selected tanker not available');
return;
}
final selectedTanker = filteredTankers[selectedTankerIndex!];
// Call your API here
// await _assignTankerApi(selectedTanker.id);
if (context.mounted) Navigator.pop(context);
},
child: Container(
decoration: BoxDecoration(
color: const Color(0XFF8270DB),
borderRadius: BorderRadius.circular(24),
),
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(
'Assign',
style: fontTextStyle(
14,
const Color(0XFFFFFFFF),
FontWeight.w500,
),
),
),
),
),
],
),
);
},
);
},
),
);
},
);
}
@override
Widget build(BuildContext context) {
@ -596,10 +851,10 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
padding: EdgeInsets.symmetric(vertical: 10),
),
onPressed: () async {
_showAssignDriverBottomSheet();
_showAssignTankerBottomSheet();
},
child: Text(
"Assign Driver",
"Assign Tanker",
style: fontTextStyle(
14, const Color(0XFFFFFFFF), FontWeight.w400),
),
@ -691,3 +946,139 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
);
}
}
// ====== TankersCard ======
class TankersCard extends StatelessWidget {
final String title;
final String subtitle;
final String capacity;
final String code;
final String owner;
final List<String> status;
const TankersCard({
super.key,
required this.title,
required this.subtitle,
required this.capacity,
required this.code,
required this.owner,
required this.status,
});
Color _chipColor(String s) {
switch (s) {
case 'filled':
return const Color(0xFFFFFFFF);
case 'available':
return const Color(0xFFE8F0FF);
case 'empty':
return const Color(0xFFFFEEEE);
case 'in-use':
return const Color(0xFFFFF0E6);
case 'maintenance':
return const Color(0xFFFFF4E6);
default:
return const Color(0xFFECECEC);
}
}
Color _chipTextColor(String s) {
switch (s) {
case 'filled':
return const Color(0xFF1D7AFC);
case 'available':
return const Color(0xFF0A9E04);
case 'empty':
return const Color(0xFFE2483D);
case 'in-use':
return const Color(0xFFEA843B);
case 'maintenance':
return const Color(0xFFD0AE3C);
default:
return Colors.black87;
}
}
@override
Widget build(BuildContext context) {
ImageProvider avatarProvider =
(AppSettings.profilePictureUrl != '' && AppSettings.profilePictureUrl != 'null')
? NetworkImage(AppSettings.profilePictureUrl)
: const AssetImage("images/profile_pic.png") as ImageProvider;
Widget _statusChip(String s) {
final chipTextColor = _chipTextColor(s);
return Container(
margin: const EdgeInsets.only(left: 6),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: _chipColor(s),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: chipTextColor, width: 1),
),
child: Text(s, style: fontTextStyle(10, chipTextColor, FontWeight.w400)),
);
}
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// 👉 First line: Avatar + Text + Status
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
radius: 12,
backgroundImage: avatarProvider,
),
const SizedBox(width: 8),
Expanded(
child: Text(
"$subtitle$title",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(12, const Color(0xFF343637), FontWeight.w600),
),
),
const SizedBox(width: 8),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 160),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
reverse: true,
child: Row(
children: status.map(_statusChip).toList(),
),
),
),
],
),
const SizedBox(height: 8),
/// 👉 Second line: Capacity + Owner + Code
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("$capacity L", style: fontTextStyle(10, const Color(0xFF343637), FontWeight.w600)),
Row(
children: [
Text(owner, style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400)),
const SizedBox(width: 8),
Text(code, style: fontTextStyle(10, const Color(0xFF515253), FontWeight.w400)),
],
),
],
),
],
),
);
}
}

@ -0,0 +1,669 @@
import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/plans/edit_plan_requests.dart';
class AcceptPlanRequests extends StatefulWidget {
var order;
var status;
AcceptPlanRequests({this.order, this.status});
@override
State<AcceptPlanRequests> createState() => _AcceptPlanRequestsState();
}
class _AcceptPlanRequestsState extends State<AcceptPlanRequests> {
int advancePayable = 0;
int advance =0;
double amountToPayAfterDelivery = 0.0;
double totalFare = 0.0;
@override
void initState() {
// TODO: implement initState
super.initState();
advance = 150;
advancePayable = advance;
//totalFare = advance + double.parse(widget.order.quoted_amount)??0;
amountToPayAfterDelivery = totalFare - advancePayable;
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
extendBodyBehindAppBar: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
title: Text(
'',
style: fontTextStyle(16, Color(0XFF2A2A2A), FontWeight.w600),
),
iconTheme: IconThemeData(color: Color(0XFF2A2A2A)),
actions: [
Row(
children: [
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 10, 10),
child: IconButton(
icon: Image.asset(
'images/help_appbar.png',
height: 20,
width: 20,
color: Color(0XFFFFFFFF),
),
onPressed: () {},
),
)
],
)
],
leading: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Padding(
padding:
const EdgeInsets.fromLTRB(8, 8, 8, 8), // Add padding if needed
child: Image.asset(
'images/backbutton_appbar.png', // Replace with your image path
fit: BoxFit.contain,
color: Color(0XFFFFFFFF),
height: 24,
width: 24,
),
),
),
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// 🔹 Top Card with Image
Stack(
clipBehavior: Clip.none,
children: [
/// 🔹 Background Image
ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(0),
bottomRight: Radius.circular(0),
),
child: Image.asset(
"images/building.png",
height: 220,
width: double.infinity,
fit: BoxFit.cover,
),
),
/// 🔹 Floating Info Card (half on image, half below)
Positioned(
bottom: -40, // pulls the card out by 40px
left: 12,
right: 12,
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: widget.status.statusColor,
width: 0.5,
),
),
child: Text(widget.status.status,
style: fontTextStyle(
12,
widget.status.statusColor,
FontWeight.w500)),
),
Text(
widget.order.building_name,
style: fontTextStyle(
20, const Color(0XFF2D2E30), FontWeight.w600),
),
SizedBox(height: 4),
Text(
widget.order.displayAddress,
style: fontTextStyle(
12, const Color(0XFF939495), FontWeight.w400),
),
],
),
const Spacer(),
Text(
widget.order.distanceInKm.toString() + 'Km',
style: fontTextStyle(
12, const Color(0XFF939495), FontWeight.w400),
),
],
),
),
),
],
),
SizedBox(
height: MediaQuery.of(context).size.height * .08,
),
/// 🔹 Order Details
Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"PLAN DETAILS",
style: fontTextStyle(
10, const Color(0XFF2D2E30), FontWeight.w600),
),
SizedBox(
height: MediaQuery.of(context).size.height * .011,
),
_detailTwoRow(
"Tanker Price",
"${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}",
"images/financialsBottomIcon.png",
"",
"",
""),
SizedBox(
height: MediaQuery.of(context).size.height * .02,
),
_detailTwoRow(
"Water Type",
"${widget.order.type_of_water}",
"images/water.png",
"Capacity",
"${widget.order.capacity}",
"images/capacity.png",
),
SizedBox(
height: MediaQuery.of(context).size.height * .02,
),
_detailTwoRow(
"Start Date",
"${widget.order.averageTime}",
"images/time.png",
"End Date",
"${widget.order.averageTime}",
"images/advance.png",
),
SizedBox(
height: MediaQuery.of(context).size.height * .02,
),
_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),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"ADDITIONAL DETAILS",
style: fontTextStyle(
10, const Color(0XFF2D2E30), FontWeight.w600),
),
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),
),
],
)),
/// 🔹 Payment Summary
Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"PAYMENT SUMMARY",
style: fontTextStyle(
10, const Color(0XFF2D2E30), FontWeight.w600),
),
SizedBox(
height: MediaQuery.of(context).size.height * .011,
),
_detailRow("Tanker Price",
"${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}"),
SizedBox(
height: MediaQuery.of(context).size.height * .004,
),
_detailRow("Booking Charges", "" + advance.toString()),
SizedBox(
height: MediaQuery.of(context).size.height * .004,
),
_detailRow("Total Amount", "" + totalFare.toString()),
SizedBox(
height: MediaQuery.of(context).size.height * .004,
),
Divider(
color: Color(0XFF646566),
thickness: 0.3,
),
SizedBox(
height: MediaQuery.of(context).size.height * .004,
),
_detailRow("Booking Charges Payable",
'${AppSettings.formDouble(advancePayable.toString()) ?? ''}'),
SizedBox(
height: MediaQuery.of(context).size.height * .004,
),
_detailRow("Amount to Pay (After Delivery)",
'${AppSettings.formDouble(amountToPayAfterDelivery.toString()) ?? ''}'),
],
),
),
const SizedBox(height: 80), // space for bottom buttons
],
),
),
/// 🔹 Bottom Action Buttons
bottomNavigationBar: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Color(0XFFFFFFFF),
border: Border(top: BorderSide(color: Color(0XFFF5F6F6))),
),
child: Row(
children: [
Expanded(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: Color(0XFF000000),
backgroundColor: Color(0xFFF1F1F1),
side: BorderSide(color: Color(0xFFF1F1F1)),
padding:
EdgeInsets.symmetric(vertical: 10), // uniform height
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => EditPlanRequests(
order: widget.order,
advance: advance.toString(),
status: widget.status,
),
),
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('images/edit.png', height: 20, width: 20),
SizedBox(width: 4),
Text(
"Edit Plan",
style: fontTextStyle(
14, const Color(0XFF000000), FontWeight.w400),
),
],
),
),
),
SizedBox(width: 8),
Expanded(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: Color(0XFFE2483D),
backgroundColor: Colors.white,
side: BorderSide(color: Color(0XFFE2483D)),
padding: EdgeInsets.symmetric(vertical: 10),
),
onPressed: ()async {
/*AppSettings.preLoaderDialog(context);
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"] = 'reject';
bool status = await AppSettings.acceptOrderRequests(
payload, widget.order.dbId);
try {
if (status) {
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longSuccessToast("Order request rejected Successfully");
Navigator.pop(context, true);
}
else{
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("reject of order request Failed");
}
} catch (e) {
Navigator.of(context,rootNavigator: true).pop();
print(e);
}
}
else{
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Please Check internet");
}*/
showRejectBottomSheet(context);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('images/cross.png', height: 20, width: 20),
SizedBox(width: 4),
Text(
"Reject",
style: fontTextStyle(
14, const Color(0XFF000000), FontWeight.w400),
),
],
),
),
),
SizedBox(width: 8),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0XFF0A9E04),
foregroundColor: Color(0XFFFFFFFF),
padding: EdgeInsets.symmetric(vertical: 10),
),
onPressed: () async {
AppSettings.preLoaderDialog(context);
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';
bool status = await AppSettings.acceptOrderRequests(
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 order request Failed");
}
} catch (e) {
Navigator.of(context,rootNavigator: true).pop();
print(e);
}
}
else{
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Please Check internet");
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('images/rite.png', height: 20, width: 20),
SizedBox(width: 4),
Text(
"Accept",
style: fontTextStyle(
14, const Color(0XFFFFFFFF), FontWeight.w400),
),
],
),
),
),
],
)),
);
}
/// 🔹 Helper widget for rows
Widget _detailRow(String title, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: fontTextStyle(12, const Color(0XFF646566), FontWeight.w400),
),
Text(
value,
style: fontTextStyle(12, const Color(0XFF2D2E30), FontWeight.w500),
),
],
),
);
}
Widget _detailTwoRow(
String title1,
String value1,
String path1,
String title2,
String value2,
String path2, {
EdgeInsetsGeometry padding = const EdgeInsets.symmetric(vertical: 6),
}) {
final titleStyle = fontTextStyle(12, Color(0XFF646566), FontWeight.w400);
final valueStyle = fontTextStyle(12, Color(0XFF343637), FontWeight.w500);
Widget _col(String t, String v, String path) {
return Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (path.isNotEmpty)
Image.asset(
path,
fit: BoxFit.contain,
height: 20,
width: 20,
color: const Color(0XFFC3C4C4),
),
if (path.isNotEmpty) const SizedBox(width: 6),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(t,
style: titleStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis),
Text(v,
style: valueStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis),
],
),
)
],
),
),
);
}
return Padding(
padding: padding,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_col(title1, value1, path1),
_col(title2, value2, path2),
],
),
);
}
Future<String?> showRejectBottomSheet(BuildContext context) {
final reasons = <String>[
"I got a better deal",
"The price is not reasonable",
"I do not have the capacity to deliver",
"I do not have drivers to deliver",
"Others",
];
String? selectedReason;
return showModalBottomSheet<String?>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (ctx) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: StatefulBuilder(
builder: (context, setState) {
return SafeArea(
top: false,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// drag handle
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 20),
Text(
"REASON FOR REJECTION",
style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600),
),
const SizedBox(height: 8),
// reasons
...reasons.map(
(reason) => RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(
reason,
style: fontTextStyle(14, const Color(0XFF2D2E30), FontWeight.w400),
),
value: reason,
activeColor: const Color(0XFFE2483D),
groupValue: selectedReason,
onChanged: (v) => setState(() => selectedReason = v),
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.pop(context, null),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
side: const BorderSide(color:Color(0XFFFFFFFF)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
),
child: Text(
"Cancel",
style: fontTextStyle(14, const Color(0XFF939495), FontWeight.w600),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: selectedReason == null
? null
: () => Navigator.pop(context, selectedReason),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFE2483D),
disabledBackgroundColor: Color(0xFFE2483D),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
),
child: Text(
"Reject Request",
style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600),
),
),
),
],
),
],
),
);
},
),
);
},
);
}
}

@ -138,7 +138,7 @@ class _AllPlansState extends State<AllPlans> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const PlanRequestPage()),
builder: (context) => const PlanRequestsPage()),
);
},
child: Text(

@ -1,66 +1,86 @@
import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart';
class EditPlanRequests extends StatefulWidget {
final dynamic order; // Pass order data here
const EditPlanRequests({super.key, this.order});
var order;
String? advance;
var status;
EditPlanRequests({this.order,this.advance,this.status});
@override
State<EditPlanRequests> createState() => _EditPlanRequestsState();
}
class _EditPlanRequestsState extends State<EditPlanRequests> {
final TextEditingController tankerPriceController =
TextEditingController(text: "2000");
final TextEditingController waterTypeController =
TextEditingController(text: "Drinking Water");
final TextEditingController frequencyController =
TextEditingController(text: "4/week");
final TextEditingController capacityController =
TextEditingController(text: "10000L");
final TextEditingController timeController =
TextEditingController(text: "10:00 AM - 5:00 PM");
final TextEditingController quantityController =
TextEditingController(text: "1/day");
final TextEditingController advanceController =
TextEditingController(text: "40%");
final TextEditingController startDateController =
TextEditingController(text: "12 July 2025");
final TextEditingController endDateController =
TextEditingController(text: "21 July 2025");
final TextEditingController tankerPriceController = TextEditingController();
final TextEditingController waterTypeController = TextEditingController();
final TextEditingController capacityController = TextEditingController();
final TextEditingController timeController = TextEditingController();
final TextEditingController quantityController = TextEditingController();
final TextEditingController advanceController = TextEditingController();
final TextEditingController dateController = TextEditingController();
@override
void initState() {
super.initState();
tankerPriceController.text='${widget.order.quoted_amount}';
waterTypeController.text='${widget.order.type_of_water}';
quantityController.text='${widget.order.quantity}';
capacityController.text='${widget.order.capacity}';
timeController.text='${widget.order.averageTime}';
dateController.text='${widget.order.time}';
advanceController.text='${widget.advance}';
// Update summary in real-time as user types
tankerPriceController.addListener(() => setState(() {}));
/*capacityController.addListener(() => setState(() {}));
quantityController.addListener(() => setState(() {}));
dateController.addListener(() => setState(() {}));
timeController.addListener(() => setState(() {}));
waterTypeController.addListener(() => setState(() {}));*/
advanceController.addListener(() => setState(() {}));
}
@override
Widget build(BuildContext context) {
int tankerPrice = int.tryParse(tankerPriceController.text) ?? 0;
int totalWeeks = 12; // static for now
int weeklyPrice = tankerPrice * 4; // assuming 4 tankers/week
int totalPrice = weeklyPrice * totalWeeks;
double advancePercent =
double.tryParse(advanceController.text.replaceAll('%', '')) ?? 0;
int advancePayable = (totalPrice * (advancePercent / 100)).round();
int updatedQuantity=int.tryParse(quantityController.text) ?? 0;
String updatedCapacity=capacityController.text ?? '';
// Parse booking/advance as a double to accept "150.0" then convert to int (round)
double bookingChargesDouble = double.tryParse(advanceController.text.trim()) ?? 0.0;
int bookingCharges = bookingChargesDouble.round(); // or .toInt() / .floor() as you prefer
int totalPrice = (tankerPrice * updatedQuantity) + bookingCharges;
int advancePayable = bookingCharges; //
int amountToPayAfterDelivery=totalPrice-bookingCharges;
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.close, color: Colors.black),
onPressed: () => Navigator.pop(context),
scrolledUnderElevation: 0,
title: Text(
'Edit Plan',
style: fontTextStyle(16, Color(0XFF2A2A2A), FontWeight.w600),
),
title: const Text(
"Edit Order",
style: TextStyle(color: Colors.black, fontWeight: FontWeight.w600),
leading: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Padding(
padding:
const EdgeInsets.fromLTRB(16, 8, 8, 8), // Add padding if needed
child: Image.asset(
'images/cross.png', // Replace with your image path
fit: BoxFit.contain,
color: Color(0XFF2A2A2A),
height: 24,
width: 24,
),
),
),
centerTitle: true,
iconTheme: IconThemeData(color: Color(0XFF2A2A2A)),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
@ -68,98 +88,209 @@ class _EditPlanRequestsState extends State<EditPlanRequests> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 🔹 Club Info Card
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(
child: Column(
Material(
elevation: 4, // shadow depth
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white, // Material needs a background color
border: Border.all(color: Color(0XFFC9C2F0),width: 0.5),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Chip(
label: Text("New",
style: TextStyle(
color: Colors.blue,
fontSize: 12,
fontWeight: FontWeight.w600)),
backgroundColor: Color(0xFFEAF3FF),
padding: EdgeInsets.symmetric(horizontal: 4),
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: widget.status.statusColor,
width: 0.5,
),
),
child: Text(
widget.status.status,
style: fontTextStyle(12, widget.status.statusColor, FontWeight.w500)
),
),
Text("Club Kohinoor",
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w600)),
SizedBox(height: 4),
Text("Banjara Hills, Hyderabad",
style: TextStyle(color: Colors.grey, fontSize: 13)),
Text(
widget.order.building_name,
style: fontTextStyle(20, const Color(0XFF2D2E30), FontWeight.w600),
),
Text(
widget.order.displayAddress,
style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400),
),
],
),
),
const Text("5.5 Km", style: TextStyle(color: Colors.black54)),
],
const Spacer(),
Text(
widget.order.distanceInKm.toString()+'Km',
style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400),
),
],
),
),
),
const SizedBox(height: 20),
const Text("ORDER DETAILS",
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
SizedBox(height:MediaQuery.of(context).size.height * .06,),
Text("PLAN DETAILS",
style: fontTextStyle(12, const Color(0XFF2D2E30), FontWeight.w600),),
const SizedBox(height: 12),
// 🔹 Two in a row
_twoFields(tankerPriceController, "Tanker Price",
waterTypeController, "Water Type"),
_twoFields(frequencyController, "Frequency", capacityController,
"Capacity"),
_twoFields(timeController, "Time of Delivery", quantityController,
"Quantity"),
_twoFields(advanceController, "Advance", startDateController,
"Start Date"),
_twoFields(endDateController, "End Date", null, null),
_twoFields(tankerPriceController, "Tanker Price",'images/price.png',false, null, null,null,null),
_detailTwoRow(
"Water Type",
"${widget.order.type_of_water}",
"images/water.png",
"Capacity",
"${widget.order.capacity}",
"images/capacity.png",
),
SizedBox(
height: MediaQuery.of(context).size.height * .02,
),
_detailTwoRow(
"Start Date",
"${widget.order.averageTime}",
"images/time.png",
"End Date",
"${widget.order.averageTime}",
"images/advance.png",
),
SizedBox(
height: MediaQuery.of(context).size.height * .02,
),
_detailTwoRow(
"Frequency",
"${widget.order.quantity}",
"images/quantity.png",
"",
"",
""),
/*_twoFields(capacityController, "Capacity",'images/capacity.png',true,quantityController, "Frequency",'images/quantity.png',true),
_twoFields(waterTypeController, "Water Type",'images/water.png',true,advanceController, "Booking Charges",'images/advance.png',false),
_twoFields(dateController, "Date",'images/calendar_appbar.png',true,timeController, "Time Of Delivery",'images/time.png',true),
*/
const SizedBox(height: 20),
const Text("UPDATED PAYMENT SUMMARY",
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
Text("UPDATED PAYMENT SUMMARY",
style: fontTextStyle(12,Color(0XFF2D2E30),FontWeight.w600),),
const SizedBox(height: 12),
_summaryRow("Tanker Price", "$tankerPrice"),
_summaryRow("Tankers per week", "04"),
_summaryRow("Weekly Price", "$weeklyPrice"),
_summaryRow("Total weeks", "$totalWeeks"),
_summaryRow("Quantity", " $updatedQuantity"),
_summaryRow("Capacity", "$updatedCapacity"),
_summaryRow("Booking Charges", "$bookingCharges"),
_summaryRow("Total Price", "$totalPrice"),
const Divider(),
_summaryRow("Advance", advanceController.text),
_summaryRow("Advance Payable", "$advancePayable"),
_summaryRow("Booking Charges Payable", "$advancePayable"),
_summaryRow("Amount to Pay (After Delivery)", "$amountToPayAfterDelivery"),
],
),
),
bottomNavigationBar: Container(
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Colors.grey.shade300)),
color: Color(0XFFFFFFFF),
border: Border(top: BorderSide(color: Color(0XFFF5F6F6))),
),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.pop(context),
child: const Text("Cancel"),
style: OutlinedButton.styleFrom(
foregroundColor: Color(0XFF000000),
backgroundColor: Color(0xFFFFFFFF),
side: BorderSide(color: Color(0xFF939495)),
padding: EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24), // <-- radius here
),
),
onPressed: () {
Navigator.pop(context);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Cancel",
style: fontTextStyle(14, const Color(0XFF000000), FontWeight.w400),
),
],
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple,
foregroundColor: Colors.white,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: Color(0XFFFFFFFF),
backgroundColor: Color(0xFF8270DB),
side: BorderSide(color: Color(0xFF8270DB)),
padding: EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24), // <-- radius here
),
),
onPressed: () {
// 🔹 Collect updated values
print("Tanker Price: ${tankerPriceController.text}");
print("Water Type: ${waterTypeController.text}");
print("Frequency: ${frequencyController.text}");
// Save logic here
onPressed: () async{
/*AppSettings.preLoaderDialog(context);
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"] = widget.advance;
payload["action"] = 'accept';
bool status = await AppSettings.acceptOrderRequests(
payload, widget.order.dbId);
try {
if (status) {
Navigator.of(context,rootNavigator: true).pop();
Navigator.pop(context, true);
Navigator.pop(context, true);
}
else{
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Accept of order request Failed");
}
} catch (e) {
Navigator.of(context,rootNavigator: true).pop();
print(e);
}
}
else{
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Please Check internet");
}*/
},
child: const Text("Send ➤"),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Send",
style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w400),
),
SizedBox(width: 12),
Image.asset('images/send.png', height: 20, width: 20),
],
),
),
),
],
@ -169,36 +300,133 @@ class _EditPlanRequestsState extends State<EditPlanRequests> {
}
/// 🔹 Two fields side by side
Widget _twoFields(TextEditingController? controller1, String? label1,
TextEditingController? controller2, String? label2) {
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),
child: _textField(controller1, label1,path1,readOnly1!),
),
const SizedBox(width: 10),
if (controller2 != null)
Expanded(
child: _textField(controller2, label2),
child: _textField(controller2, label2,path2,readOnly2!),
),
],
);
}
Widget _detailTwoRow(
String title1,
String value1,
String path1,
String title2,
String value2,
String path2, {
EdgeInsetsGeometry padding = const EdgeInsets.symmetric(vertical: 6),
}) {
final titleStyle = fontTextStyle(12, Color(0XFF646566), FontWeight.w400);
final valueStyle = fontTextStyle(12, Color(0XFF343637), FontWeight.w500);
Widget _col(String t, String v, String path) {
return Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (path.isNotEmpty)
Image.asset(
path,
fit: BoxFit.contain,
height: 20,
width: 20,
color: const Color(0XFFC3C4C4),
),
if (path.isNotEmpty) const SizedBox(width: 6),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(t,
style: titleStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis),
Text(v,
style: valueStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis),
],
),
)
],
),
),
);
}
return Padding(
padding: padding,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_col(title1, value1, path1),
_col(title2, value2, path2),
],
),
);
}
/// 🔹 Custom text field
Widget _textField(TextEditingController? controller, String? label) {
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(
labelText: label,
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,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
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,),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
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),
),
);
}
@ -210,12 +438,12 @@ class _EditPlanRequestsState extends State<EditPlanRequests> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: const TextStyle(color: Colors.black54)),
Text(title, style: fontTextStyle(12,Color(0XFF646566),FontWeight.w400),),
Text(value,
style: const TextStyle(
fontWeight: FontWeight.w600, color: Colors.black)),
style: fontTextStyle(12,Color(0XFF2D2E30),FontWeight.w500),),
],
),
);
}
}

@ -1,162 +1,192 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/orders/order_requests_model.dart';
import 'package:supplier_new/plans/accept_plan_requests.dart';
import 'package:supplier_new/plans/plan_requests_model.dart';
class PlanRequestPage extends StatefulWidget {
const PlanRequestPage({super.key});
class PlanRequestsPage extends StatefulWidget {
const PlanRequestsPage({super.key});
@override
State<PlanRequestPage> createState() => _PlanRequestPageState();
State<PlanRequestsPage> createState() => _PlanRequestsPageState();
}
class _PlanRequestPageState extends State<PlanRequestPage> {
class _PlanRequestsPageState extends State<PlanRequestsPage> {
final TextEditingController searchController = TextEditingController();
List<PlanRequestsModel> planRequestsList = [];
List<PlanRequestsModel> filteredList = [];
bool isLoading = false;
@override
void initState() {
super.initState();
_fetchPlanRequests();
// 🔹 Add listener for search
searchController.addListener(() {
_filterOrders(searchController.text);
});
}
void _filterOrders(String query) {
final lowerQuery = query.toLowerCase();
setState(() {
if (lowerQuery.isEmpty) {
filteredList = planRequestsList;
} else {
filteredList = planRequestsList.where((order) {
final title = order.building_name.toLowerCase() ?? '';
final address = order.displayAddress.toLowerCase() ?? '';
final waterType = order.type_of_water.toLowerCase() ?? '';
final capacity = order.capacity.toLowerCase() ?? '';
return title.contains(lowerQuery) ||
address.contains(lowerQuery) ||
waterType.contains(lowerQuery) ||
capacity.contains(lowerQuery);
}).toList();
}
});
}
Future<void> _fetchPlanRequests() async {
setState(() => isLoading = true);
try {
final response = await AppSettings.getPlanRequestsFromUsers();
final data = (jsonDecode(response)['data'] as List)
.map((e) => PlanRequestsModel.fromJson(e))
.toList();
if (!mounted) return;
setState(() {
planRequestsList = data;
filteredList = data;
isLoading = false;
});
} catch (e) {
debugPrint("⚠️ Error fetching orders: $e");
setState(() => isLoading = false);
}
}
/// 🔹 Helper to get status based on order time
Map<String, dynamic> getOrderStatus(String orderTimeStr, String dbStatus) {
String status = "Invalid time";
Color color = Colors.grey;
final dbLower = (dbStatus ?? "").toLowerCase().trim();
// Statuses that ignore time
if (dbLower == "reject") return {"status": "Rejected", "color": const Color(0XFFE2483D)};
if (dbLower == "accept") return {"status": "Accepted", "color": const Color(0XFF0A9E04)};
if (dbLower == "advance_paid") return {"status": "Accepted", "color": const Color(0XFF0A9E04)};
if (dbLower == "cancelled" || dbLower == "cancelled_by_user" || dbLower == "cancelled_by_supplier") {
return {"status": "Cancelled", "color": const Color(0XFF757575)};
}
// Time-based status (only if not rejected/accepted/etc.)
if (orderTimeStr.isEmpty) {
// fallback for pending without time
return {"status": "Pending", "color": const Color(0XFFE56910)};
}
try {
final format = DateFormat("dd-MM-yyyy HH:mm");
final orderTime = format.parse(orderTimeStr);
final now = DateTime.now();
final difference = now.difference(orderTime);
if (difference.inDays < 2) {
status = "New";
color = const Color(0XFF1D7AFC); // Blue
} else if (difference.inDays < 10) {
final remaining = 20 - difference.inDays;
status = "Expires in ${remaining} D"; // show time for pending
color = difference.inDays < 10
? const Color(0XFFE56910)
: const Color(0XFFE2483D);
} else {
// Expired overrides pending
status = "Expired";
color = const Color(0XFF757575);
}
} catch (e) {
debugPrint("⚠️ Error parsing time: $e");
status = "Invalid time";
color = Colors.grey;
}
return {"status": status, "color": color};
}
// Sample orders list
final List<PlanModel> allPlans = [
PlanModel(
status: "New",
title: "Green Valley Apartments",
location: "Gachibowli",
description: "10,000 L - Drinking water",
price: "₹3,400",
),
PlanModel(
status: "Expires in 15m",
title: "Lakeview Towers",
location: "Madhapur",
description: "8,000 L - Borewell water",
price: "₹2,700",
),
PlanModel(
status: "Expired",
title: "Sunrise Residency",
location: "Kukatpally",
description: "12,000 L - Tanker water",
price: "₹4,000",
),
PlanModel(
status: "Rejected",
title: "Skyline Apartments",
location: "Kompally",
description: "5,000 L - Drinking water",
price: "₹1,600",
),
PlanModel(
status: "Pending",
title: "Skyline Apartments",
location: "Kompally",
description: "5,000 L - Drinking water",
price: "₹1,600",
),
PlanModel(
status: "Rejected",
title: "Elite Towers",
location: "Hitech City",
description: "20,000 L - Borewell water",
price: "₹6,000",
),
];
@override
Widget build(BuildContext context) {
// Split orders into rejected and others
final rejectedPlans =
allPlans.where((o) => o.status.toLowerCase() == "rejected").toList();
final otherPlans =
allPlans.where((o) => o.status.toLowerCase() != "rejected").toList();
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => Navigator.pop(context),
),
title: const Text(
"Plan Requests",
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
centerTitle: false,
actions: [
IconButton(
icon: const Icon(Icons.help_outline, color: Colors.black),
onPressed: () {},
),
const SizedBox(width: 8),
],
),
appBar: AppSettings.SupplierAppBarWithHelpAction('Plan Requests', context),
body: Padding(
padding: const EdgeInsets.all(12.0),
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
/// Search bar
Row(
children: [
Expanded(
child: Container(
height: 42,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
border: Border.all(color: Colors.grey.shade300),
color: Colors.white,
),
child: TextField(
controller: searchController,
decoration: const InputDecoration(
hintText: "Search",
prefixIcon: Icon(Icons.search, color: Colors.grey),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 10),
),
),
),
),
const SizedBox(width: 10),
Container(
height: 42,
width: 42,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
color: Colors.white,
),
child: IconButton(
icon: const Icon(Icons.sort, size: 20, color: Colors.black),
onPressed: () {},
),
),
],
),
/// 🔹 Search Bar
_buildSearchBar(),
const SizedBox(height: 16),
/// Orders List
/// 🔹 Orders List
Expanded(
child: ListView(
children: [
// Active / Other Orders
...otherPlans.map((o) => OrderCard(order: o)),
// Rejected Orders Section
if (rejectedPlans.isNotEmpty) ...[
const SizedBox(height: 12),
const Text(
"Rejected Requests",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
const SizedBox(height: 8),
...rejectedPlans.map((o) => OrderCard(order: o)),
],
],
child: isLoading
? const Center(child: CircularProgressIndicator())
: filteredList .isEmpty?Center(
child: Text(
'No Data Available',
style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700),
),
):ListView.builder(
itemCount: filteredList .length,
itemBuilder: (context, index) {
final order = filteredList [index];
final statusMap = getOrderStatus(order.time ?? "", order.status ?? "");
final status = statusMap['status'];
final color = statusMap['color'];
final cardModel = OrderCardModel(
status: status,
statusColor: color,
title: order.building_name ?? "",
location: order.displayAddress ?? "",
description: "${order.capacity ?? ''} - ${order.type_of_water ?? ''}",
price: "${AppSettings.formDouble(order.quoted_amount) ?? ''}",
);
final noNavigateStatuses = ["expired", "rejected", "accepted", "advance_paid", "cancelled"];
final disableNavigation = noNavigateStatuses.contains(status.toLowerCase());
final card = OrderCard(order: cardModel);
return disableNavigation
? card // just show the card, no tap
: GestureDetector(
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AcceptPlanRequests(order: order, status: cardModel),
),
);
// If result indicates API reload
if (result == true) {
_fetchPlanRequests();
}
},
child: card,
);
},
),
),
],
@ -164,18 +194,98 @@ class _PlanRequestPageState extends State<PlanRequestPage> {
),
);
}
Widget _buildSearchBar() {
return Row(
children: [
Expanded(
child: Container(
height: 42,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0XFF939495)),
),
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: "Search",
hintStyle: fontTextStyle(16, const Color(0XFF646566), FontWeight.w400),
prefixIcon: SizedBox(
height: 20,
width: 20,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
'images/search.png',
fit: BoxFit.contain,
),
),
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 10),
),
),
),
),
const SizedBox(width: 10),
Container(
height: 42,
width: 42,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
color: Color(0XFFF5F6F6),
),
child: IconButton(
icon: Image.asset(
'images/filter.png', // your image path
height: 20,
width: 20,
fit: BoxFit.contain,
),
onPressed: () {
// Your onPressed action
},
)
),
const SizedBox(width: 5),
Container(
height: 42,
width: 42,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
color: Color(0XFFF5F6F6),
),
child: IconButton(
icon: Image.asset(
'images/sort.png', // your image path
height: 20,
width: 20,
fit: BoxFit.contain,
),
onPressed: () {
// Your onPressed action
},
)
),
],
);
}
}
/// Order Model
class PlanModel {
/// 🔹 UI Model for rendering OrderCard
class OrderCardModel {
final String status;
final Color statusColor;
final String title;
final String location;
final String description;
final String price;
PlanModel({
OrderCardModel({
required this.status,
required this.statusColor,
required this.title,
required this.location,
required this.description,
@ -183,109 +293,73 @@ class PlanModel {
});
}
/// Order Card widget
/// 🔹 Card widget
class OrderCard extends StatelessWidget {
final PlanModel order;
final OrderCardModel order;
const OrderCard({super.key, required this.order});
Color _getStatusColor() {
switch (order.status.toLowerCase()) {
case "new":
return Colors.blue;
case "expires in 15m":
case "expires in 5m":
return Colors.orange;
case "expired":
return Colors.grey;
case "rejected":
return Colors.red;
default:
return Colors.black;
}
}
@override
Widget build(BuildContext context) {
final statusColor = _getStatusColor();
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
color: Color(0XFFF6F6F6),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
/// Left content
/// 🔹 Left content
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Status chip
/// Status chip
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
color: Color(0XFFF6F6F6),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: order.statusColor,
width: 0.5,
),
),
child: Text(
order.status,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: statusColor,
),
order.status,
style: fontTextStyle(12, order.statusColor, FontWeight.w500)
),
),
const SizedBox(height: 6),
SizedBox(height:MediaQuery.of(context).size.height * .008,),
// Title
/// Title
Text(
order.title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
order.title,
style: fontTextStyle(16, Color(0XFF2D2E30), FontWeight.w600)
),
// Location
/// Location
Text(
order.location,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
order.location,
style: fontTextStyle(10, Color(0XFF939495), FontWeight.w400)
),
const SizedBox(height: 4),
// Description
/// Description
Text(
order.description,
style: const TextStyle(
fontSize: 12,
color: Colors.blue,
fontWeight: FontWeight.w500,
),
order.description,
style: fontTextStyle(14, Color(0XFF8270DB), FontWeight.w500)
),
],
),
),
/// Price
/// 🔹 Price
Text(
order.price,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
order.price,
style: fontTextStyle(16, Color(0XFF444444), FontWeight.w600)
),
],
),

@ -0,0 +1,87 @@
import 'package:supplier_new/common/settings.dart';
import 'package:geolocator/geolocator.dart';
class PlanRequestsModel {
String building_name = '';
String address = '';
String type_of_water = '';
String capacity = '';
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 frequency='';
PlanRequestsModel();
factory PlanRequestsModel.fromJson(Map<String, dynamic> json){
PlanRequestsModel rtvm = new PlanRequestsModel();
// rtvm.building_name = json['customer_details']['buildingName'] ?? '';
rtvm.dbId = json['_id']?? '';
// rtvm.address = json['customer_details']['profile']['address1'] ?? '';
rtvm.type_of_water = json['type_of_water'] ?? '';
rtvm.capacity = json['capacity'] ?? '';
//rtvm.quantity = json['quantity']?? '';
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";
}
rtvm.averageTime = json['time'] ?? '';
rtvm.time = json['my_supplier_entry']['time'] ?? '';
rtvm.status = json['my_supplier_entry']['status'] ?? '';
//rtvm.quoted_amount = json['my_supplier_entry']['quoted_amount'].toString() ?? '';
//rtvm.lng=json['customer_details']['longitude'] ?? 0.0;
//rtvm.lat=json['customer_details']['latitude'] ?? 0.0;
// 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),
);
return rtvm;
}
Map<String, dynamic> toJson() => {
"boreName":this.building_name,
};
}
Loading…
Cancel
Save