master
Sneha 2 months ago
parent 8ebdc2b505
commit 37a5d3ce83

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Before

Width:  |  Height:  |  Size: 945 B

After

Width:  |  Height:  |  Size: 945 B

@ -153,10 +153,11 @@ class AppSettings{
static String uploadPicUrl = host + 'uploads-user'; static String uploadPicUrl = host + 'uploads-user';
static String getOrderRequestsFromUsersUrl = host + 'getuserRequestbookingsforsupplier'; static String getOrderRequestsFromUsersUrl = host + 'getuserRequestbookingsforsupplier';
static String acceptOrderRequestsUrl = host + 'request-booking-with-charges'; static String acceptOrderRequestsUrl = host + 'request-booking-with-charges';
static String getAcceptedOrdersFromUsersUrl = host + 'supplier/booking/advance-paid'; static String getAcceptedOrdersFromUsersUrl = host + 'getAllTankersBookingdetails';
static String getPlanRequestsFromUsersUrl = host + 'getuserRequestbookingsforplansforsupplier'; static String getPlanRequestsFromUsersUrl = host + 'getuserRequestbookingsforplansforsupplier';
static String getTankersUrl = host + 'getTankers'; static String getTankersUrl = host + 'getTankers';
static String addTankerUrl = host + 'addTankers'; static String addTankerUrl = host + 'addTankers';
static String updateTankerUrl = host + 'updateTankers';
static String getDriversUrl = host + 'getalldeliveryboys'; static String getDriversUrl = host + 'getalldeliveryboys';
static String addDriversUrl = host + 'addDeliveryboys'; static String addDriversUrl = host + 'addDeliveryboys';
static String addSourceLocationsUrl = host + 'addSource'; static String addSourceLocationsUrl = host + 'addSource';
@ -164,7 +165,7 @@ class AppSettings{
static String setRatesDailyUrl = host + 'tankers'; static String setRatesDailyUrl = host + 'tankers';
static String getSupplierDetailsUrl = host + 'suppliers'; static String getSupplierDetailsUrl = host + 'suppliers';
static String updatePumpFeeUrl = host + 'suppliers'; static String updatePumpFeeUrl = host + 'suppliers';
static String assignTankerUrl = host + 'suppliers'; static String assignTankerUrl = host + 'assign-deliveryboy-tanker';
@ -542,6 +543,40 @@ class AppSettings{
} }
} }
static Future<bool> updateTanker(payload,tankerName) async {
var uri = Uri.parse(updateTankerUrl+'/'+supplierId);
uri = uri.replace(query: 'tankerName=$tankerName');
var response = await http.put(uri,
body: json.encode(payload), headers: await buildRequestHeaders());
if (response.statusCode == 200) {
try {
var _response = json.decode(response.body);
print(_response);
return true;
} catch (e) {
// display error toast
return false;
}
} else if (response.statusCode == 401) {
bool status = await AppSettings.resetToken();
if (status) {
response = await http.put(uri,
body: json.encode(payload), headers: await buildRequestHeaders());
if (response.statusCode == 200) {
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
static Future<String> getDrivers() async { static Future<String> getDrivers() async {
var uri = Uri.parse(getDriversUrl+'/'+supplierId); var uri = Uri.parse(getDriversUrl+'/'+supplierId);
//uri = uri.replace(query: 'supplierId=$supplierId'); //uri = uri.replace(query: 'supplierId=$supplierId');

@ -364,7 +364,7 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
), ),
Image.asset("images/icon_tune.png", width: 24, height: 24), Image.asset("images/icon_tune.png", width: 24, height: 24),
const SizedBox(width: 16), const SizedBox(width: 16),
Image.asset("images/up_down arrow.png", width: 24, height: 24), Image.asset("images/up_down_arrow.png", width: 24, height: 24),
], ],
), ),
), ),

@ -367,7 +367,7 @@ class _AcceptOrderRequestsState extends State<AcceptOrderRequests> {
payload["supplierId"] = AppSettings.supplierId; payload["supplierId"] = AppSettings.supplierId;
payload["amount"] = int.parse(widget.order.quoted_amount); payload["amount"] = int.parse(widget.order.quoted_amount);
payload["delivery_charges"] = advance; payload["delivery_charges"] = advance;
payload["action"] = 'reject'; payload["action"] = 'rejected';
bool status = await AppSettings.acceptOrderRequests( bool status = await AppSettings.acceptOrderRequests(
payload, widget.order.dbId); payload, widget.order.dbId);
@ -424,7 +424,7 @@ class _AcceptOrderRequestsState extends State<AcceptOrderRequests> {
payload["supplierId"] = AppSettings.supplierId; payload["supplierId"] = AppSettings.supplierId;
payload["amount"] = int.parse(widget.order.quoted_amount); payload["amount"] = int.parse(widget.order.quoted_amount);
payload["delivery_charges"] = advance; payload["delivery_charges"] = advance;
payload["action"] = 'accept'; payload["action"] = 'accepted';
bool status = await AppSettings.acceptOrderRequests( bool status = await AppSettings.acceptOrderRequests(
payload, widget.order.dbId); payload, widget.order.dbId);

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/orders/delivery_updates.dart';
import 'package:supplier_new/orders/order_requests.dart'; import 'package:supplier_new/orders/order_requests.dart';
import 'package:supplier_new/orders/orders_model.dart'; import 'package:supplier_new/orders/orders_model.dart';
import 'package:supplier_new/orders/search_order_appbar.dart'; import 'package:supplier_new/orders/search_order_appbar.dart';
@ -9,6 +10,7 @@ import 'package:intl/intl.dart';
import 'assign_driver.dart'; import 'assign_driver.dart';
import 'cancel_order.dart'; import 'cancel_order.dart';
import 'change_driver.dart';
class AllOrders extends StatefulWidget { class AllOrders extends StatefulWidget {
final String navigationFrom; final String navigationFrom;
@ -294,29 +296,42 @@ class OrderCard extends StatelessWidget {
const OrderCard({super.key, required this.order, this.onRefresh}); const OrderCard({super.key, required this.order, this.onRefresh});
Color _getStatusColor() { Color _getStatusColor() {
switch (order.status.toLowerCase()) { String st='';
if(order.status.toLowerCase()=='advance_paid'){
st='pending';
}
else{
st=order.status.toLowerCase();
}
switch (st.toLowerCase()) {
case "completed": case "completed":
return Color(0XFFC4E8C3); return Color(0XFFC4E8C3);
case "in-progress": case "in_progress":
return Color(0XFFE8F2FF); return Color(0XFFC9DFFE);
case "cancelled": case "cancelled":
return Color(0XFFFCEDEC); return Color(0XFFFCEDEC);
case "assigned": case "assigned":
return Color(0XFFF9DBC6); return Color(0XFFF9DBC6);
case "pending": case "pending":
return Color(0XFFFDF3D3); return Color(0XFFFDF3D3);
case "advance_paid":
return Color(0XFFFDF3D3);
default: default:
return Colors.grey; return Colors.grey;
} }
} }
Color _getTextStatusColor() { Color _getTextStatusColor() {
switch (order.status.toLowerCase()) { String st='';
if(order.status.toLowerCase()=='advance_paid'){
st='pending';
}
else{
st=order.status.toLowerCase();
}
switch (st.toLowerCase()) {
case "completed": case "completed":
return Color(0XFF0A9E04); return Color(0XFF0A9E04);
case "in-progress": case "in_progress":
return Color(0XFF1D7AFC); return Color(0XFF1D7AFC);
case "cancelled": case "cancelled":
return Color(0XFFE2483D); return Color(0XFFE2483D);
@ -324,8 +339,6 @@ class OrderCard extends StatelessWidget {
return Color(0XFFE56910); return Color(0XFFE56910);
case "pending": case "pending":
return Color(0XFFD0AE3C); return Color(0XFFD0AE3C);
case "advance_paid":
return Color(0XFFD0AE3C);
default: default:
return Colors.grey; return Colors.grey;
} }
@ -336,7 +349,10 @@ class OrderCard extends StatelessWidget {
final statusColor = _getStatusColor(); final statusColor = _getStatusColor();
final textStatusColor = _getTextStatusColor(); final textStatusColor = _getTextStatusColor();
return Padding(padding: EdgeInsets.fromLTRB(0, 8, 0, 0), String st = (order.status == 'advance_paid') ? 'pending' : order.status;
return Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
child: Container( child: Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -350,31 +366,33 @@ class OrderCard extends StatelessWidget {
) )
], ],
), ),
/// 👇 IntrinsicHeight allows both sides of the row to match height dynamically
child: IntrinsicHeight(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, // 👈 image stretches
children: [ children: [
// Image /// 🖼 Left Image
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12), topLeft: Radius.circular(12),
bottomLeft: Radius.circular(0), bottomLeft: Radius.circular(12),
), ),
child: order.imageAsset != '' child: SizedBox(
width: 145, // fixed width
child: order.imageAsset.isNotEmpty
? Image.asset( ? Image.asset(
order.imageAsset, order.imageAsset,
height: 145, fit: BoxFit.cover, // 👈 fills height
width: 145,
fit: BoxFit.cover,
) )
: Image.network( : Image.network(
order.imageAsset, order.imageAsset,
height: 100,
width: 100,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
),
// Right side content /// 📄 Right Content
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
@ -392,48 +410,50 @@ class OrderCard extends StatelessWidget {
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
child: Text( child: Text(
order.status, st,
style: fontTextStyle(10,textStatusColor,FontWeight.w400) style: fontTextStyle(10, textStatusColor, FontWeight.w400),
), ),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Text( Text(
order.building_name, order.building_name,
style:fontTextStyle(16,Color(0XFF2D2E30),FontWeight.w600) style: fontTextStyle(16, const Color(0XFF2D2E30), FontWeight.w600),
), ),
Text( Text(
order.displayAddress, order.displayAddress,
style: fontTextStyle(12,Color(0XFF646566),FontWeight.w400) style: fontTextStyle(12, const Color(0XFF646566), FontWeight.w400),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
order.capacity+' - '+order.type_of_water, '${order.capacity} - ${order.type_of_water}',
style: fontTextStyle(14,Color(0XFF444444),FontWeight.w500) style: fontTextStyle(14, const Color(0XFF444444), FontWeight.w500),
), ),
Text( Text(
order.time+' , '+order.date, '${order.time} , ${order.date}',
style:fontTextStyle(8,Color(0XFF646566),FontWeight.w400) style: fontTextStyle(8, const Color(0XFF646566), FontWeight.w400),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Row(
/// ===================
/// 🟡 ASSIGN & CANCEL BUTTONS
/// ===================
Visibility(
visible: order.status.toLowerCase() == 'advance_paid',
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Visibility( GestureDetector(
visible: order.status.toLowerCase().toString()=='advance_paid', onTap: () async {
child: GestureDetector(
onTap: ()async{
final result = await Navigator.push( final result = await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => AssignDriverScreen(order: order), builder: (context) => AssignDriverScreen(order: order),
), ),
); );
// If result indicates API reload
if (result == true) { if (result == true) {
onRefresh?.call(); // 👈 safe call to refresh parent onRefresh?.call();
} }
}, },
child: Container( child: Container(
@ -441,66 +461,177 @@ class OrderCard extends StatelessWidget {
borderRadius: BorderRadius.circular(22), borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0XFF939495)), border: Border.all(color: const Color(0XFF939495)),
), ),
child: Padding( child: const Padding(
padding: EdgeInsets.fromLTRB(8,4,8,4), padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
child: Text( child: Text(
"Assign", "Assign",
style: fontTextStyle( style: TextStyle(
14, const Color(0XFF515253), FontWeight.w400), fontSize: 14,
color: Color(0XFF515253),
fontWeight: FontWeight.w400),
),
),
),
),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DeliveryUpdatesPage(
orderId: order.dbId,
initialStatus: order.status,
),
),
);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0XFFE2483D)),
color: const Color(0XFFE2483D),
),
child: const Padding(
padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
child: Text(
"Cancel",
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w400),
), ),
)
), ),
),), ),
),
],
),
),
/// ===================
/// 🟣 TRACK DELIVERY BUTTON
/// ===================
Visibility( Visibility(
visible: order.status.toLowerCase().toString()=='advance_paid', visible: order.status.toLowerCase() == 'in_progress',
child: GestureDetector( child: GestureDetector(
onTap: ()async{ onTap: () async {
final result = await Navigator.push( final result = await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => CancelOrderScreen(order: order), builder: (context) => AssignDriverScreen(order: order),
), ),
); );
// If result indicates API reload
if (result == true) { if (result == true) {
onRefresh?.call(); // 👈 safe call to refresh parent onRefresh?.call();
} }
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22), borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0XFFE2483D)), color: const Color(0XFF8270DB),
color: Color(0XFFE2483D) border: Border.all(color: const Color(0XFF8270DB)),
), ),
child: Padding( child: const Padding(
padding: EdgeInsets.fromLTRB(8,4,8,4), padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
child: Text( child: Text(
"Cancel", "Track Delivery",
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w400),
),
),
),
),
),
/// ===================
/// 🟣 TRACK DELIVERY BUTTON
/// ===================
Visibility(
visible: order.status.toLowerCase() == 'assigned',
/* child: */
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'images/avatar.png',
fit: BoxFit.cover,
width: 12,
height: 12,
),
const SizedBox(width: 8),
// Title + Chips
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
"Assigned to ${order.delivery_agent_name}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle( style: fontTextStyle(
14, const Color(0XFFFFFFFF), FontWeight.w400), 8, const Color(0xFF646566), FontWeight.w400),
), ),
)
), ),
),),
], ],
) ),
],
),
),
/*Text( ],
order.extraInfo, ),
style:fontTextStyle(12, order.status == "in-progress" SizedBox(height: MediaQuery.of(context).size.height * .004),
? Color(0XFF1D7AFC) GestureDetector(
: Color(0XFF646566),FontWeight.w400) onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeDriverScreen(order: order),
),
);
if (result == true) {
onRefresh?.call();
}
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
color: const Color(0XFF8270DB),
border: Border.all(color: const Color(0XFF8270DB)),
),
child: const Padding(
padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
child: Text(
"Change",
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w400),
),
),
),
),
],
)
),
),*/
], ],
), ),
), ),
), ),
], ],
), ),
),); ),
),
);
} }
} }

@ -129,8 +129,8 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
children: [ children: [
Text( Text(
"Assign Tanker", "Assign Tanker",
style: fontTextStyle( style: fontTextStyle(16,
16, const Color(0XFF2A2A2A), FontWeight.w600), const Color(0XFF2A2A2A), FontWeight.w600),
), ),
GestureDetector( GestureDetector(
onTap: () => Navigator.pop(context), onTap: () => Navigator.pop(context),
@ -157,7 +157,8 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
child: Row( child: Row(
children: [ children: [
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Text( Text(
widget.order.building_name, widget.order.building_name,
@ -197,7 +198,8 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
const SizedBox(height: 12), const SizedBox(height: 12),
isTankersDataLoading isTankersDataLoading
? const Center(child: CircularProgressIndicator()) ? const Center(
child: CircularProgressIndicator())
: (tankersList.isEmpty : (tankersList.isEmpty
? Center( ? Center(
child: Text( child: Text(
@ -210,12 +212,14 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
) )
: ListView.separated( : ListView.separated(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics:
const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
itemCount: tankersList itemCount: tankersList
.where((t) => .where((t) =>
_capToLiters(t.capacity) == _capToLiters(t.capacity) ==
_capToLiters(widget.order.capacity)) _capToLiters(
widget.order.capacity))
.length, .length,
separatorBuilder: (_, __) => separatorBuilder: (_, __) =>
const SizedBox(height: 12), const SizedBox(height: 12),
@ -223,7 +227,8 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
final filteredTankers = tankersList final filteredTankers = tankersList
.where((t) => .where((t) =>
_capToLiters(t.capacity) == _capToLiters(t.capacity) ==
_capToLiters(widget.order.capacity)) _capToLiters(
widget.order.capacity))
.toList(); .toList();
final d = filteredTankers[idx]; final d = filteredTankers[idx];
final isSelected = final isSelected =
@ -233,7 +238,8 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
onTap: () { onTap: () {
setModalState(() { setModalState(() {
selectedTankerIndex = idx; selectedTankerIndex = idx;
selectedDriverIndex = null; // reset driver selection if tanker changes selectedDriverIndex =
null; // reset driver selection if tanker changes
}); });
}, },
child: Card( child: Card(
@ -280,22 +286,28 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0XFFFFFFFF), color: const Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFC3C4C4)), border: Border.all(
color: const Color(0xFFC3C4C4)),
), ),
child: Center( child: Center(
child: Text( child: Text(
'Select a tanker to choose driver', 'Select a tanker to choose driver',
style: fontTextStyle( style: fontTextStyle(
14, const Color(0xFF2D2E30), FontWeight.w400), 14,
const Color(0xFF2D2E30),
FontWeight.w400),
), ),
), ),
) )
: isLoading : isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(
child: CircularProgressIndicator())
: (driversList.isEmpty : (driversList.isEmpty
? Center( ? Center(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12), padding:
const EdgeInsets.symmetric(
vertical: 12),
child: Text( child: Text(
'No Data Available', 'No Data Available',
style: fontTextStyle( style: fontTextStyle(
@ -307,21 +319,26 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
) )
: ListView.separated( : ListView.separated(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics:
const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
itemCount: driversList.length, itemCount: driversList.length,
separatorBuilder: (_, __) => const SizedBox(height: 12), separatorBuilder: (_, __) =>
const SizedBox(height: 12),
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
final d = driversList[idx]; final d = driversList[idx];
final isSelected = selectedDriverIndex == idx; final isSelected =
final isAvailable = d.status == "available"; selectedDriverIndex == idx;
final isAvailable =
d.status == "available";
final statusColor = isAvailable final statusColor = isAvailable
? const Color(0XFF0A9E04) ? const Color(0XFF0A9E04)
: (d.status == "on delivery" : (d.status == "on delivery"
? const Color(0XFFD0AE3C) ? const Color(0XFFD0AE3C)
: (d.status == "offline" : (d.status == "offline"
? const Color(0XFF939495) ? const Color(
0XFF939495)
: Colors.grey)); : Colors.grey));
return GestureDetector( return GestureDetector(
@ -340,17 +357,25 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
}); });
}, },
child: Card( child: Card(
color: const Color(0XFFFFFFFF), color:
const Color(0XFFFFFFFF),
elevation: 1, elevation: 1,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius:
BorderRadius.circular(
12),
side: BorderSide( side: BorderSide(
color: isSelected ? primaryColor : const Color(0XFFC3C4C4), color: isSelected
? primaryColor
: const Color(
0XFFC3C4C4),
width: 1, width: 1,
), ),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.all(12.0), padding:
const EdgeInsets.all(
12.0),
child: Row( child: Row(
children: [ children: [
Image.asset( Image.asset(
@ -359,24 +384,45 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
width: 20, width: 20,
height: 20, height: 20,
), ),
const SizedBox(width: 8), const SizedBox(
width: 8),
Expanded( Expanded(
child: Text( child: Text(
d.driver_name, d.driver_name,
style: fontTextStyle( style: fontTextStyle(
14, const Color(0XFF2D2E30), FontWeight.w500), 14,
const Color(
0XFF2D2E30),
FontWeight
.w500),
), ),
), ),
const SizedBox(width: 8), const SizedBox(
width: 8),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), padding:
decoration: BoxDecoration( const EdgeInsets
borderRadius: BorderRadius.circular(4), .symmetric(
border: Border.all(color: statusColor), horizontal: 6,
vertical: 2),
decoration:
BoxDecoration(
borderRadius:
BorderRadius
.circular(
4),
border: Border.all(
color:
statusColor),
), ),
child: Text( child: Text(
d.status, d.status,
style: fontTextStyle(10, statusColor, FontWeight.w400), style:
fontTextStyle(
10,
statusColor,
FontWeight
.w400),
), ),
), ),
], ],
@ -395,20 +441,17 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
SafeArea( SafeArea(
top: false, top: false,
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 16, vertical: 12), horizontal: 16, vertical: 12),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
if (selectedTankerIndex == null) { if (selectedTankerIndex == null) {
AppSettings.longFailedToast('Please select tanker'); AppSettings.longFailedToast(
'Please select tanker');
return; return;
} }
/*if (selectedDriverIndex == null) {
AppSettings.longFailedToast('Please select driver');
return;
}*/
final filteredTankers = tankersList final filteredTankers = tankersList
.where((t) => .where((t) =>
@ -416,55 +459,55 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
_capToLiters(widget.order.capacity)) _capToLiters(widget.order.capacity))
.toList(); .toList();
final selectedTanker = filteredTankers[selectedTankerIndex!]; final selectedTanker =
final selectedDriver = driversList[selectedDriverIndex!]; filteredTankers[selectedTankerIndex!];
final selectedDriver = selectedDriverIndex != null
? driversList[selectedDriverIndex!]
: null;
AppSettings.preLoaderDialog(context); AppSettings.preLoaderDialog(context);
bool isOnline =
bool isOnline = await AppSettings.internetConnectivity(); await AppSettings.internetConnectivity();
if (isOnline) { if (isOnline) {
var payload = new Map<String, dynamic>(); var payload = <String, dynamic>{};
payload["tankerName"] = selectedTanker.tanker_name; payload["tankerName"] =
if(selectedDriverIndex != null){ selectedTanker.tanker_name;
payload["delivery_agent"] = selectedDriver.driver_name; payload["delivery_agent"] =
payload["delivery_agent_mobile"] = selectedDriver.phone_number; selectedDriver?.driver_name;
} payload["delivery_agent_mobile"] =
else{ selectedDriver?.phone_number;
payload["delivery_agent"] =null;
payload["delivery_agent_mobile"] = null;
}
bool status = await AppSettings.assignTanker(
payload, widget.order.dbId);
bool status = await AppSettings.assignTanker(payload, widget.order.dbId); Navigator.of(context, rootNavigator: true)
.pop(); // close loader
try {
if (status) { if (status) {
Navigator.of(context,rootNavigator: true).pop(); AppSettings.longSuccessToast(
AppSettings.longFailedToast("Tanker assigned successfully"); "Tanker assigned successfully");
Navigator.pop(context, true);
// 👇 Pop bottom sheet first
Navigator.pop(context);
// 👇 Then pop the AssignDriverScreen and send true to parent
Navigator.pop(context, true);
} else {
AppSettings.longFailedToast(
"Failed to assign tanker");
} }
else{ } else {
Navigator.of(context,rootNavigator: true).pop(); Navigator.of(context, rootNavigator: true)
AppSettings.longFailedToast("Failed to assign tanker"); .pop();
} AppSettings.longFailedToast(
} catch (e) { "Please Check internet");
Navigator.of(context,rootNavigator: true).pop();
print(e);
}
}
else{
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Please Check internet");
} }
if (context.mounted) Navigator.pop(context);
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0XFF8270DB), color: const Color(0XFF8270DB),
borderRadius: BorderRadius.circular(24), borderRadius : BorderRadius.circular(24),
), ),
alignment: Alignment.center, alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
@ -954,7 +997,8 @@ class TankersCard extends StatelessWidget {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: chipTextColor, width: 1), border: Border.all(color: chipTextColor, width: 1),
), ),
child: Text(s, style: fontTextStyle(10, chipTextColor, FontWeight.w400)), child:
Text(s, style: fontTextStyle(10, chipTextColor, FontWeight.w400)),
); );
} }
@ -991,7 +1035,8 @@ class TankersCard extends StatelessWidget {
"$subtitle$title", "$subtitle$title",
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500), style: fontTextStyle(
14, const Color(0xFF2D2E30), FontWeight.w500),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@ -1009,7 +1054,8 @@ class TankersCard extends StatelessWidget {
), ),
Text( Text(
code, code,
style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400), style: fontTextStyle(
10, const Color(0xFF646566), FontWeight.w400),
), ),
], ],
), ),
@ -1019,6 +1065,5 @@ class TankersCard extends StatelessWidget {
], ],
), ),
); );
} }
} }

@ -1,10 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/orders/edit_order_requests.dart';
import '../resources/drivers_model.dart'; import '../resources/drivers_model.dart';
import '../resources/tankers_model.dart';
class ChangeDriverScreen extends StatefulWidget { class ChangeDriverScreen extends StatefulWidget {
var order; var order;
@ -20,12 +18,18 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
double amountToPayAfterDelivery = 0.0; double amountToPayAfterDelivery = 0.0;
double totalFare = 0.0; double totalFare = 0.0;
bool isLoading = false; bool isLoading = false;
bool isTankersDataLoading = false;
List<DriversModel> driversList = []; List<DriversModel> driversList = [];
List<TankersModel> tankersList = [];
TankersModel? orderTanker;
DriversModel? orderDriver;
@override @override
void initState() { void initState() {
// TODO: implement initState // TODO: implement initState
super.initState(); super.initState();
_fetchTankers();
_fetchDrivers(); _fetchDrivers();
advance = 150; advance = 150;
advancePayable = advance; advancePayable = advance;
@ -41,8 +45,21 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
.map((e) => DriversModel.fromJson(e)) .map((e) => DriversModel.fromJson(e))
.toList(); .toList();
if (!mounted) return; if (!mounted) return;
// 🔎 Match driver by name (case-insensitive, trimmed)
final wanted = (widget.order.delivery_agent_name ?? '').toString().trim().toLowerCase();
DriversModel? matched;
for (final d in data) {
final name = (d.driver_name ?? '').toString().trim().toLowerCase();
if (name == wanted) {
matched = d;
break;
}
}
setState(() { setState(() {
driversList = data; driversList = data;
orderDriver = matched; // store matched driver or null
isLoading = false; isLoading = false;
}); });
} catch (e) { } catch (e) {
@ -51,34 +68,319 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
} }
} }
void _showAssignDriverBottomSheet() { Future<void> _fetchTankers() async {
int? selectedDriverIndex; // Track selected driver 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;
// 🔎 Find tanker by name (case-insensitive, trimmed)
final wanted = (widget.order.tanker_name ?? '').toString().trim().toLowerCase();
TankersModel? matched;
for (final t in data) {
final name = (t.tanker_name ?? '').toString().trim().toLowerCase();
if (name == wanted) {
matched = t;
break;
}
}
setState(() {
tankersList = data;
orderTanker = matched; // <- store the matched tanker (or null if not found)
isTankersDataLoading = false;
});
} catch (e) {
debugPrint("⚠️ Error fetching tankers: $e");
setState(() => isTankersDataLoading = false);
}
}
Widget _assignedTankerDetails() {
if (isTankersDataLoading) {
return const Center(child: CircularProgressIndicator());
}
// If not found, show a simple fallback using just the name from order
if (orderTanker == null) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0XFFFFFFFF)),
),
child: Row(
children: [
Image.asset('images/square_avatar.png', width: 24, height: 24),
const SizedBox(width: 8),
Expanded(
child: Text(
widget.order.tanker_name ?? "Tanker",
style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
// No details available
],
),
);
}
// Found: show full details from fetched TankersModel
final t = orderTanker!;
final availability = (t.availability ?? []).cast<String>();
String? primaryStatus;
const pref = ['in-use', 'available', 'filled', 'empty', 'maintenance'];
for (final s in pref) {
if (availability.any((x) => x.toLowerCase() == s)) { primaryStatus = s; break; }
}
primaryStatus ??= availability.isNotEmpty ? availability.first : null;
Color _statusTextColor(String s) {
switch (s.toLowerCase()) {
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 const Color(0xFF2A2A2A);
}
}
Widget _statusChip(String label) {
final c = _statusTextColor(label);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(8),
border: Border.all(color: c, width: 1),
),
child: Text(label, style: fontTextStyle(10, c, FontWeight.w400)),
);
}
Widget _line(String title, String value) {
return Padding(
padding: const EdgeInsets.only(top: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: fontTextStyle(12, const Color(0xFF646566), FontWeight.w400)),
Flexible(
child: Text(value,
style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w500),
textAlign: TextAlign.right, overflow: TextOverflow.ellipsis, maxLines: 1),
),
],
),
);
}
return Container(
padding: const EdgeInsets.all(0),
decoration: BoxDecoration(
color: const Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0XFFFFFFFF)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Image.asset('images/avatar.png', width: 24, height: 24),
const SizedBox(width: 8),
Expanded(
child: Text(
t.tanker_name ?? "",
style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500),
maxLines: 1, overflow: TextOverflow.ellipsis,
),
),
if (primaryStatus != null) _statusChip(primaryStatus),
],
),
const SizedBox(height: 8),
Visibility(
visible:(t.license_plate!="") ,
child: Text(
t.license_plate ?? "",
style: fontTextStyle(12, const Color(0XFF646566), FontWeight.w400),
maxLines: 1, overflow: TextOverflow.ellipsis,
),)
/* _line("License Plate", t.license_plate ?? ""),
_line("Water Type", t.type_of_water ?? ""),
_line("Capacity", t.capacity ?? ""),
if ((t.supplier_name ?? "").isNotEmpty) _line("Owner", t.supplier_name),*/
],
),
);
}
Widget _assignedDriverDetails() {
if (isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (orderDriver == null) {
// fallback if not found
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0XFFC3C4C4)),
),
child: Row(
children: [
Image.asset('images/avatar.png', width: 24, height: 24),
const SizedBox(width: 8),
Expanded(
child: Text(
widget.order.delivery_agent_name ?? "Driver",
style: fontTextStyle(14, const Color(0XFF2D2E30), FontWeight.w500),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
final d = orderDriver!;
Color _statusTextColor(String s) {
switch (s.toLowerCase()) {
case 'available': return const Color(0XFF0A9E04);
case 'on delivery': return const Color(0XFFD0AE3C);
case 'offline': return const Color(0XFF939495);
default: return greyColor;
}
}
Widget _statusChip(String label) {
final c = _statusTextColor(label);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: c, width: 1),
),
child: Text(label, style: fontTextStyle(10, c, FontWeight.w400)),
);
}
Widget _line(String title, String value) {
return Padding(
padding: const EdgeInsets.only(top: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: fontTextStyle(12, const Color(0xFF646566), FontWeight.w400)),
Flexible(
child: Text(
value,
style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w500),
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
);
}
return Container(
padding: const EdgeInsets.all(0),
decoration: BoxDecoration(
color: const Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0XFFFFFFFF)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Image.asset('images/avatar.png', width: 24, height: 24),
const SizedBox(width: 8),
Expanded(
child: Text(
d.driver_name ?? "",
style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500),
maxLines: 1, overflow: TextOverflow.ellipsis,
),
),
if ((d.status ?? "").isNotEmpty) _statusChip(d.status),
],
),
/* const SizedBox(height: 8),
_line("Phone", d.phone_number ?? ""),*/
],
),
);
}
void _showAssignTankerBottomSheet() {
// 🔸 Find default selected indexes
int? selectedTankerIndex;
int? selectedDriverIndex;
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 (s.contains('kl')) return (n * 1000).round();
return n.round();
}
// 🧠 Preselect assigned tanker
final filteredTankers = tankersList
.where((t) => _capToLiters(t.capacity) == _capToLiters(widget.order.capacity))
.toList();
selectedTankerIndex = filteredTankers.indexWhere(
(t) => t.tanker_name == widget.order.tanker_name);
// 🧠 Preselect assigned driver
selectedDriverIndex = driversList.indexWhere(
(d) => d.driver_name == widget.order.delivery_agent_name);
showModalBottomSheet( showModalBottomSheet(
backgroundColor: const Color(0XFFFFFFFF),
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
isDismissible: false, isDismissible: false,
enableDrag: false, enableDrag: true,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)), borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
), ),
builder: (context) { builder: (context) {
return WillPopScope( // block Android back button return WillPopScope(
onWillPop: () async => false, onWillPop: () async => false,
child: StatefulBuilder( child: StatefulBuilder(
builder: (context, setModalState) { builder: (context, setModalState) {
return DraggableScrollableSheet( return FractionallySizedBox(
expand: false, heightFactor: 0.95,
initialChildSize: 0.7, child: Column(
minChildSize: 0.5, children: [
maxChildSize: 0.9, Expanded(
builder: (context, scrollController) { child: SingleChildScrollView(
return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -97,73 +399,132 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
"Assign Driver", "Edit Order",
style: fontTextStyle(16, const Color(0XFF2A2A2A), FontWeight.w600), style: fontTextStyle(16,
const Color(0XFF2A2A2A), FontWeight.w600),
), ),
GestureDetector( GestureDetector(
onTap: (){ onTap: () => Navigator.pop(context),
Navigator.pop(context); child: Image.asset(
}, 'images/cross.png',
child: Image.asset('images/cross.png', height: 24, width: 24,color: Color(0XFF2A2A2A),), height: 24,
width: 24,
color: const Color(0XFF2A2A2A),
),
) )
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// 🏢 Order Info /// 🛻 Tanker List
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( Text(
widget.order.building_name, "SELECTED TANKER",
style: fontTextStyle(20, const Color(0XFF2D2E30), FontWeight.w600), style: fontTextStyle(
10, const Color(0XFF2D2E30), FontWeight.w600),
), ),
Text( const SizedBox(height: 12),
widget.order.displayAddress, isTankersDataLoading
style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400), ? 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(
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: filteredTankers.length,
separatorBuilder: (_, __) =>
const SizedBox(height: 12),
itemBuilder: (context, idx) {
final t = filteredTankers[idx];
final isSelected =
selectedTankerIndex == idx;
return GestureDetector(
onTap: () {
setModalState(() {
selectedTankerIndex = idx;
selectedDriverIndex = null;
});
},
child: Card(
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(12),
side: BorderSide(
color: isSelected
? primaryColor
: const Color(0XFFC3C4C4),
width: 1,
), ),
const Spacer(),
Text(
'${widget.order.distanceInKm} Km',
style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400),
), ),
], child: TankersCard(
title: t.tanker_name,
subtitle: t.type_of_water,
capacity: t.capacity,
code: t.license_plate,
owner: t.supplier_name,
status: List<String>.from(
t.availability),
), ),
), ),
);
},
)),
const SizedBox(height: 16), const SizedBox(height: 16),
/// 🧍 Driver List
Text( Text(
"SELECT DRIVER", "SELECTED DRIVER",
style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600), style: fontTextStyle(
10, const Color(0XFF2D2E30), FontWeight.w600),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// 🧍 Driver List selectedTankerIndex == null
Expanded( ? Container(
child: isLoading width: double.infinity,
? const Center(child: CircularProgressIndicator()) padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(top: 8),
decoration: BoxDecoration(
color: const Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: const Color(0xFFC3C4C4)),
),
child: Center(
child: Text(
'Select a tanker to choose driver',
style: fontTextStyle(
14,
const Color(0xFF2D2E30),
FontWeight.w400),
),
),
)
: ListView.separated( : ListView.separated(
controller: scrollController, shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: driversList.length, itemCount: driversList.length,
separatorBuilder: (_, __) => const SizedBox(height: 12), separatorBuilder: (_, __) =>
const SizedBox(height: 12),
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
final d = driversList[idx]; final d = driversList[idx];
final bool isSelected = selectedDriverIndex == idx; final isSelected =
final bool isAvailable = d.status == "available"; selectedDriverIndex == idx;
final statusColor = d.status == "available"
final statusColor = isAvailable
? const Color(0XFF0A9E04) ? const Color(0XFF0A9E04)
: (d.status == "on delivery" : (d.status == "on delivery"
? const Color(0XFFD0AE3C) ? const Color(0XFFD0AE3C)
@ -173,31 +534,23 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
if (isAvailable) { // only selectable if available
setModalState(() { setModalState(() {
selectedDriverIndex = idx; selectedDriverIndex = idx;
}); });
} else {
AppSettings.longFailedToast(
'Only available drivers can be selected',
);
}
}, },
child: Opacity(
opacity: isAvailable ? 1 : 1, // 👈 grey out non-available
child: Card( child: Card(
color: const Color(0XFFFFFFFF),
elevation: 1,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius:
BorderRadius.circular(12),
side: BorderSide( side: BorderSide(
color: isSelected color: isSelected
? const Color(0XFF8270DB) ? primaryColor
: const Color(0XFFC3C4C4), : const Color(0XFFC3C4C4),
width: 1, width: 1,
), ),
), ),
color: isSelected
? const Color(0XFFEDEBFF)
: Colors.white,
child: Padding( child: Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: Row( child: Row(
@ -208,64 +561,103 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
width: 20, width: 20,
height: 20, height: 20,
), ),
SizedBox( const SizedBox(width: 8),
width: MediaQuery.of(context).size.width * .016),
Expanded( Expanded(
child: Text( child: Text(
d.driver_name, d.driver_name,
style: fontTextStyle( style: fontTextStyle(
14, 14,
const Color(0XFF2D2E30), const Color(
FontWeight.w500, 0XFF2D2E30),
), FontWeight.w500),
), ),
), ),
SizedBox( const SizedBox(width: 8),
width: MediaQuery.of(context).size.width * .016),
Container( Container(
padding: const EdgeInsets.symmetric( padding:
horizontal: 6, vertical: 2), const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), borderRadius:
border: Border.all(color: statusColor), BorderRadius.circular(4),
border: Border.all(
color: statusColor),
), ),
child: Text( child: Text(
d.status, d.status,
style: fontTextStyle( style: fontTextStyle(
10, 10,
statusColor, statusColor,
FontWeight.w400, FontWeight.w400),
),
), ),
), ),
], ],
), ),
), ),
), ),
),
); );
}, },
), ),
],
),
),
), ),
const SizedBox(height: 16), /// Assign Button
SafeArea(
// 🟣 Assign Button top: false,
SizedBox( child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
child: SizedBox(
width: double.infinity, width: double.infinity,
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
if (selectedDriverIndex == null) { if (selectedTankerIndex == null) {
AppSettings.longFailedToast('Please select driver'); AppSettings.longFailedToast(
'Please select tanker');
return; return;
} }
final selectedDriver = driversList[selectedDriverIndex!]; final selectedTanker =
filteredTankers[selectedTankerIndex!];
final selectedDriver = selectedDriverIndex != null
? driversList[selectedDriverIndex!]
: null;
// Call your API here AppSettings.preLoaderDialog(context);
// await _assignDriverApi(selectedDriver.driver_id); bool isOnline =
await AppSettings.internetConnectivity();
if (context.mounted) Navigator.pop(context); if (isOnline) {
var payload = <String, dynamic>{};
payload["tankerName"] =
selectedTanker.tanker_name;
payload["delivery_agent"] =
selectedDriver?.driver_name;
payload["delivery_agent_mobile"] =
selectedDriver?.phone_number;
bool status = await AppSettings.assignTanker(
payload, widget.order.dbId);
Navigator.of(context, rootNavigator: true).pop();
if (status) {
AppSettings.longSuccessToast(
"Tanker assigned successfully");
Navigator.pop(context);
Navigator.pop(context, true);
} else {
AppSettings.longFailedToast(
"Failed to assign tanker");
}
} else {
Navigator.of(context, rootNavigator: true).pop();
AppSettings.longFailedToast(
"Please Check internet");
}
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -277,7 +669,9 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
child: Text( child: Text(
'Assign', 'Assign',
style: fontTextStyle( style: fontTextStyle(
14, const Color(0XFFFFFFFF), FontWeight.w500), 14, Colors.white, FontWeight.w500),
),
),
), ),
), ),
), ),
@ -286,20 +680,15 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
), ),
); );
}, },
),
); );
}, },
),);
},
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
@ -523,42 +912,30 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
style: fontTextStyle( style: fontTextStyle(
12, const Color(0XFF646566), FontWeight.w400), 12, const Color(0XFF646566), FontWeight.w400),
), ),
Image.asset(
'images/avatar.png',
fit: BoxFit.cover,
width: 20,
height: 20,
),
SizedBox( SizedBox(
width: MediaQuery.of(context).size.width * .016), height: MediaQuery.of(context).size.height * .032,
Expanded(
child: Text(
'd.driver_name',
style: fontTextStyle(
14,
const Color(0XFF2D2E30),
FontWeight.w500,
), ),
Text(
"ASSIGNED TANKER",
style: fontTextStyle(
10, const Color(0XFF646566), FontWeight.w600),
), ),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
), ),
_assignedTankerDetails(),
SizedBox( SizedBox(
width: MediaQuery.of(context).size.width * .016), height: MediaQuery.of(context).size.height * .012,
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.green),
), ),
child: Text( Text(
'd.status', "ASSIGNED TO",
style: fontTextStyle( style: fontTextStyle(
10, 10, const Color(0XFF646566), FontWeight.w600),
Colors.green,
FontWeight.w400,
),
), ),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
), ),
_assignedDriverDetails()
], ],
)), )),
], ],
@ -583,7 +960,7 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
padding: EdgeInsets.symmetric(vertical: 10), padding: EdgeInsets.symmetric(vertical: 10),
), ),
onPressed: () async { onPressed: () async {
AppSettings.preLoaderDialog(context); /*AppSettings.preLoaderDialog(context);
bool isOnline = await AppSettings.internetConnectivity(); bool isOnline = await AppSettings.internetConnectivity();
@ -615,7 +992,8 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
} else { } else {
Navigator.of(context, rootNavigator: true).pop(); Navigator.of(context, rootNavigator: true).pop();
AppSettings.longFailedToast("Please Check internet"); AppSettings.longFailedToast("Please Check internet");
} }*/
Navigator.pop(context);
}, },
child: Text( child: Text(
"CANCEL", "CANCEL",
@ -633,10 +1011,10 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
padding: EdgeInsets.symmetric(vertical: 10), padding: EdgeInsets.symmetric(vertical: 10),
), ),
onPressed: () async { onPressed: () async {
_showAssignDriverBottomSheet(); _showAssignTankerBottomSheet();
}, },
child: Text( child: Text(
"Change Order", "Edit Order",
style: fontTextStyle( style: fontTextStyle(
14, const Color(0XFFFFFFFF), FontWeight.w400), 14, const Color(0XFFFFFFFF), FontWeight.w400),
), ),
@ -728,3 +1106,144 @@ class _ChangeDriverScreenState extends State<ChangeDriverScreen> {
); );
} }
} }
// ====== 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(0xFFFFFFFF);
case 'empty':
return const Color(0xFFFFFFFF);
case 'in-use':
return const Color(0xFFFFFFFF);
case 'maintenance':
return const Color(0xFFFFFFFF);
default:
return const Color(0xFFFFFFFF);
}
}
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 Color(0xFF2A2A2A);
}
}
@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(8),
decoration: BoxDecoration(
color: const Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0XFFC3C4C4)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'images/square_avatar.png',
fit: BoxFit.cover,
width: 24,
height: 24,
),
const SizedBox(width: 8),
// Title + Chips
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
"$subtitle$title",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(
14, const Color(0xFF2D2E30), FontWeight.w500),
),
),
const SizedBox(width: 8),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 160),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
reverse: true,
child: Row(
children: status.map(_statusChip).toList(),
),
),
),
],
),
Text(
code,
style: fontTextStyle(
10, const Color(0xFF646566), FontWeight.w400),
),
],
),
),
],
),
],
),
);
}
}

@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
class DeliveryUpdatesPage extends StatefulWidget {
final String orderId;
final String initialStatus;
const DeliveryUpdatesPage({super.key, required this.orderId, required this.initialStatus});
@override
State<DeliveryUpdatesPage> createState() => _DeliveryUpdatesPageState();
}
class _DeliveryUpdatesPageState extends State<DeliveryUpdatesPage> {
List<String> statuses = [
"Tanker reached source",
"Water filling started",
"Water filling completed",
"Tanker started to customer location",
"Offloading water started",
"Offloading water completed",
"Payment completed",
"Delivery completed"
];
int currentStep = 0;
@override
void initState() {
super.initState();
// Example: Get live updates from backend (MQTT, WebSocket, Firestore, etc.)
_simulateStatusUpdates();
}
void _simulateStatusUpdates() async {
// This is just simulation replace with your listener
for (int i = 0; i < statuses.length; i++) {
await Future.delayed(const Duration(seconds: 3));
setState(() {
currentStep = i;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Track Order"),
backgroundColor: const Color(0XFF0A9E04),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: statuses.length,
itemBuilder: (context, index) {
final isCompleted = index <= currentStep;
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
// Circle with tanker icon or checkmark
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: isCompleted ? Colors.green : Colors.grey[300],
shape: BoxShape.circle,
),
child: Center(
child: index == currentStep
? const Icon(Icons.local_shipping)
: Icon(
isCompleted
? Icons.check
: Icons.circle_outlined,
size: 16,
color: isCompleted ? Colors.white : Colors.grey,
),
),
),
if (index != statuses.length - 1)
Container(
width: 4,
height: 50,
color: index < currentStep ? Colors.green : Colors.grey[300],
),
],
),
const SizedBox(width: 12),
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
statuses[index],
style: TextStyle(
fontSize: 14,
fontWeight: isCompleted ? FontWeight.w600 : FontWeight.w400,
color: isCompleted ? Colors.black : Colors.grey[600],
),
),
),
)
],
);
},
),
),
// 🔸 Current status shown at bottom
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
"Current Status: ${statuses[currentStep]}",
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
),
),
],
),
),
);
}
}

@ -21,6 +21,8 @@ class OrdersModel {
String status=''; String status='';
String date=''; String date='';
String imageAsset='images/building.png'; String imageAsset='images/building.png';
String delivery_agent_name = '';
String tanker_name = '';
OrdersModel(); OrdersModel();
@ -39,6 +41,8 @@ class OrdersModel {
rtvm.quoted_amount = json['price'].toString() ?? ''; rtvm.quoted_amount = json['price'].toString() ?? '';
rtvm.lng=json['longitude'] ?? 0.0; rtvm.lng=json['longitude'] ?? 0.0;
rtvm.lat=json['latitude'] ?? 0.0; rtvm.lat=json['latitude'] ?? 0.0;
rtvm.delivery_agent_name = json['delivery_agent'] ?? '';
rtvm.tanker_name = json['tankerName'] ?? '';
// Split and trim // Split and trim
List<String> parts = rtvm.address.split(',').map((e) => e.trim()).toList(); List<String> parts = rtvm.address.split(',').map((e) => e.trim()).toList();

@ -509,7 +509,7 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
const SizedBox(width: 16), const SizedBox(width: 16),
Image.asset("images/icon_tune.png", width: 24, height: 24), Image.asset("images/icon_tune.png", width: 24, height: 24),
const SizedBox(width: 16), const SizedBox(width: 16),
Image.asset("images/up_down arrow.png", width: 24, height: 24), Image.asset("images/up_down_arrow.png", width: 24, height: 24),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),

@ -36,7 +36,6 @@ class FirstCharUppercaseFormatter extends TextInputFormatter {
); );
} }
} }
void main() => runApp(const MaterialApp(home: ResourcesFleetScreen()));
class ResourcesFleetScreen extends StatefulWidget { class ResourcesFleetScreen extends StatefulWidget {
const ResourcesFleetScreen({super.key}); const ResourcesFleetScreen({super.key});
@ -570,13 +569,16 @@ class _ResourcesFleetScreenState extends State<ResourcesFleetScreen> {
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
final it = filtered[idx]; final it = filtered[idx];
return GestureDetector( return GestureDetector(
onTap: (){ onTap: () async{
Navigator.push( final result = await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => TankerDetailsPage(tankerDetails: it), builder: (context) => TankerDetailsPage(tankerDetails: it),
), ),
); );
if (result == true) {
_fetchTankers();
}
}, },
child: TankCard( child: TankCard(
title: it.tanker_name, title: it.tanker_name,
@ -735,21 +737,7 @@ class TankCard extends StatelessWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
Row( Row(
children: [ children: [
ClipOval( Image.asset('images/avatar.png', width: 12, height: 12),
child: Container(
height: 12,
width: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: (AppSettings.profilePictureUrl != '' && AppSettings.profilePictureUrl != 'null')
? NetworkImage(AppSettings.profilePictureUrl)
: const AssetImage("images/profile_pic.png") as ImageProvider,
fit: BoxFit.cover,
),
),
),
),
const SizedBox(width: 6), const SizedBox(width: 6),
Expanded( Expanded(
child: Text(owner, style: fontTextStyle(8, const Color(0xFF646566), FontWeight.w400)), child: Text(owner, style: fontTextStyle(8, const Color(0xFF646566), FontWeight.w400)),

@ -1,4 +1,36 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../common/settings.dart';
class FirstCharUppercaseFormatter extends TextInputFormatter {
const FirstCharUppercaseFormatter();
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final text = newValue.text;
if (text.isEmpty) return newValue;
// Find first non-space char
final i = text.indexOf(RegExp(r'\S'));
if (i == -1) return newValue;
final first = text[i];
final upper = first.toUpperCase();
if (first == upper) return newValue;
final newText = text.replaceRange(i, i + 1, upper);
return newValue.copyWith(
text: newText,
selection: newValue.selection,
composing: TextRange.empty,
);
}
}
class TankerDetailsPage extends StatefulWidget { class TankerDetailsPage extends StatefulWidget {
var tankerDetails; var tankerDetails;
@ -9,50 +41,440 @@ class TankerDetailsPage extends StatefulWidget {
} }
class _TankerDetailsPageState extends State<TankerDetailsPage> { class _TankerDetailsPageState extends State<TankerDetailsPage> {
@override
Widget build(BuildContext context) { final _formKey = GlobalKey<FormState>();
return Scaffold( final _nameCtrl = TextEditingController();
backgroundColor: Colors.white, final _capacityCtrl = TextEditingController();
appBar: AppBar( final _plateCtrl = TextEditingController();
backgroundColor: Colors.white, final _mfgYearCtrl = TextEditingController();
elevation: 0, final _insExpiryCtrl = TextEditingController();
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black), // Dropdown selections (sheet)
onPressed: () => Navigator.pop(context), String? selectedType;
String? selectedTypeOfWater;
// Dropdown options (adjust to your backend)
final List<String> tankerTypes = const [
"Standard Tanker",
"High-Capacity Tanker",
"Small Tanker",
];
final List<String> typeOfWater = const [
"Drinking water",
"Bore water",
];
String? _required(String? v, {String field = "This field"}) {
if (v == null || v.trim().isEmpty) return "$field is required";
return null;
}
int selectedTab = 0;
String search = '';
bool isLoading = false;
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;
}
}
Future<void> _pickInsuranceDate() async {
final picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
dialogBackgroundColor: Colors.white,
colorScheme: Theme.of(context).colorScheme.copyWith(
primary: const Color(0xFF8270DB),
surface: Colors.white,
onSurface: const Color(0xFF101214),
),
),
child: child!,
);
},
);
if (picked != null) {
final dd = picked.day.toString().padLeft(2, '0');
final mm = picked.month.toString().padLeft(2, '0');
final yyyy = picked.year.toString();
_insExpiryCtrl.text = "$dd-$mm-$yyyy";
setState(() {});
}
}
void _resetForm() {
_formKey.currentState?.reset();
_nameCtrl.clear();
_capacityCtrl.clear();
_plateCtrl.clear();
_mfgYearCtrl.clear();
_insExpiryCtrl.clear();
selectedType = null;
selectedTypeOfWater = null;
}
String? fitToOption(String? incoming, List<String> options) {
if (incoming == null) return null;
final inc = incoming.trim();
if (inc.isEmpty) return null;
final match = options.firstWhere(
(o) => o.toLowerCase() == inc.toLowerCase(),
orElse: () => '',
);
return match.isEmpty ? null : match; // return the exact option string
}
Future<void> _updateTanker() async {
// Validate
final ok = _formKey.currentState?.validate() ?? false;
if (!ok) {
setState(() {}); // force rebuild to show errors
return;
}
// Build payload keys to match your backend
final payload = <String, dynamic>{
"tankerName": _nameCtrl.text.trim(),
"capacity": _capacityCtrl.text.trim(),
"typeofwater": selectedTypeOfWater ?? "",
"supplier_address": AppSettings.userAddress,
"supplier_name": AppSettings.userName,
"phoneNumber": AppSettings.phoneNumber,
"tanker_type": selectedType ?? "",
"license_plate": _plateCtrl.text.trim(),
"manufacturing_year": _mfgYearCtrl.text.trim(),
"insurance_exp_date": _insExpiryCtrl.text.trim(),
"delivery_fee": widget.tankerDetails.delivery_fee,
"pumping_fee":widget.tankerDetails.pumping_fee,
"price": widget.tankerDetails.price,
};
try {
final bool tankStatus = await AppSettings.updateTanker(payload,widget.tankerDetails.tanker_name);
if (!mounted) return;
if (tankStatus) {
AppSettings.longSuccessToast("Tanker Updated Successfully");
Navigator.pop(context, true); // close sheet
_resetForm();
// refresh from server
} else {
AppSettings.longFailedToast("Tanker update failed");
}
} catch (e) {
debugPrint("⚠️ update tanker error: $e");
if (!mounted) return;
AppSettings.longFailedToast("Something went wrong");
}
}
Future<void> openTankerSimpleSheet(BuildContext context) async {
_nameCtrl.text = widget.tankerDetails.tanker_name ?? '';
_capacityCtrl.text = widget.tankerDetails.capacity ?? '';
_plateCtrl.text = widget.tankerDetails.license_plate ?? '';
_mfgYearCtrl.text = widget.tankerDetails.manufacturing_year ?? '';
_insExpiryCtrl.text = widget.tankerDetails.insurance_expiry ?? '';
selectedType = fitToOption(widget.tankerDetails.tanker_type, tankerTypes);
selectedTypeOfWater = fitToOption(widget.tankerDetails.type_of_water, typeOfWater);
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
final viewInsets = MediaQuery.of(context).viewInsets.bottom;
return FractionallySizedBox(
heightFactor: 0.75,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
), ),
title: Text( child: Padding(
widget.tankerDetails.tanker_name.isNotEmpty padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + viewInsets),
? widget.tankerDetails.tanker_name[0].toUpperCase() + child: Form(
widget.tankerDetails.tanker_name.substring(1) key: _formKey,
: '', child: SingleChildScrollView(
style: const TextStyle( child: Column(
color: Colors.black, crossAxisAlignment: CrossAxisAlignment.stretch,
fontSize: 16, children: [
fontWeight: FontWeight.w600, Row(
children: [
Expanded(
child: Center(
child: Container(
width: 86,
height: 4,
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: const Color(0xFFE0E0E0),
borderRadius: BorderRadius.circular(2),
), ),
), ),
centerTitle: false, ),
actions: [ ),
TextButton( ],
onPressed: () {}, ),
child: const Text(
"HELP", _LabeledField(
style: TextStyle( label: "Tanker Name *",
color: Color(0xFF4F46E5), child: TextFormField(
fontWeight: FontWeight.w600, controller: _nameCtrl,
fontSize: 14, validator: (v) => _required(v, field: "Tanker Name"),
textCapitalization: TextCapitalization.none,
inputFormatters: const [
FirstCharUppercaseFormatter(), // << live first-letter caps
],
decoration: InputDecoration(
hintText: "Enter Tanker Name",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Tanker Capacity (in L) *",
child: TextFormField(
controller: _capacityCtrl,
validator: (v) => _required(v, field: "Tanker Capacity"),
decoration: InputDecoration(
hintText: "10,000",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9,]')),
],
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Tanker Type *",
child: DropdownButtonFormField<String>(
value: selectedType,
items: tankerTypes
.map((t) => DropdownMenuItem(value: t, child: Text(t)))
.toList(),
onChanged: (v) => setState(() => selectedType = v),
validator: (v) => v == null || v.isEmpty ? "Tanker Type is required" : null,
isExpanded: true,
alignment: Alignment.centerLeft,
hint: Text(
"Select Type",
style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
),
icon: Image.asset('images/downarrow.png', width: 16, height: 16),
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: false,
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
),
),
_LabeledField(
label: "Type of water *",
child: DropdownButtonFormField<String>(
value: (selectedTypeOfWater != null && typeOfWater.contains(selectedTypeOfWater))
? selectedTypeOfWater
: null, // <- ensure null instead of "" or a non-member
items: typeOfWater
.map((t) => DropdownMenuItem(value: t, child: Text(t)))
.toList(),
onChanged: (v) => setState(() => selectedTypeOfWater = v),
validator: (v) => v == null || v.isEmpty ? "Type of water is required" : null,
isExpanded: true,
alignment: Alignment.centerLeft,
hint: Text(
"Select type of water",
style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
),
icon: Image.asset('images/downarrow.png', width: 16, height: 16),
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: false,
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
),
),
_LabeledField(
label: "License Plate *",
child: TextFormField(
controller: _plateCtrl,
validator: (v) => _required(v, field: "License Plate"),
decoration: InputDecoration(
hintText: "AB 05 H 4948",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
textCapitalization: TextCapitalization.characters,
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Manufacturing Year (opt)",
child: TextFormField(
controller: _mfgYearCtrl,
decoration: InputDecoration(
hintText: "YYYY",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4),
],
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Insurance Expiry Date (opt)",
child: TextFormField(
controller: _insExpiryCtrl,
readOnly: true,
decoration: InputDecoration(
hintText: "DD-MM-YYYY",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
suffixIcon: const Icon(Icons.calendar_today_outlined, size: 18),
),
onTap: _pickInsuranceDate,
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8270DB),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
),
onPressed: (){
_updateTanker();
/*if (_formKey.currentState!.validate()) {
final updatedTanker = {
"tanker_name": _nameCtrl.text.trim(),
"capacity": _capacityCtrl.text.trim(),
"license_plate": _plateCtrl.text.trim(),
"manufacturing_year": _mfgYearCtrl.text.trim(),
"insurance_expiry": _insExpiryCtrl.text.trim(),
"tanker_type": selectedType,
"type_of_water": selectedTypeOfWater,
};
// You can now call your update API or local DB function
print("Updated Tanker: $updatedTanker");
Navigator.pop(context);
}*/
},
child: Text(
"Update",
style: fontTextStyle(14, Colors.white, FontWeight.w600),
), ),
), ),
), ),
], ],
), ),
),
),
),
),
);
},
);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Return true to indicate successful tanker update refresh
Navigator.pop(context, true);
return false; // prevent default pop since we manually handled it
},
child: Scaffold(
backgroundColor: Color(0XFFF1F1F1),
appBar: AppSettings.supplierAppBarWithActionsText(widget.tankerDetails.tanker_name.isNotEmpty
? widget.tankerDetails.tanker_name[0].toUpperCase() +
widget.tankerDetails.tanker_name.substring(1)
: '', context),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 🛻 Tanker Image Container(
decoration: const BoxDecoration(
color: Color(0XFFFFFFFF),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
padding: const EdgeInsets.all(16),
child: Column(
children: [
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: Stack( child: Stack(
@ -79,75 +501,207 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: widget.tankerDetails.availability
_buildStatusChip("filled", const Color(0xFF4F46E5)), .map<Widget>((s) {
const SizedBox(width: 8), final chipTextColor = _chipTextColor(s);
_buildStatusChip("available", const Color(0xFF0A9E04)), return Container(
], margin: const EdgeInsets.only(right: 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),
),
);
})
.toList(),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// 🚚 Tanker Info // 🚚 Tanker Info
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const CircleAvatar(
radius: 18,
backgroundImage: AssetImage('images/avatar.png'),
),
const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Ramesh Krishna", widget.tankerDetails.tanker_name,
style: TextStyle( style: fontTextStyle(16, const Color(0xFF2A2A2A), FontWeight.w600),
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.black,
), ),
SizedBox(height: 2),
Text(
widget.tankerDetails.type_of_water+' - '+widget.tankerDetails.capacity+' L',
style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400),
), ),
SizedBox(height: 2), SizedBox(height: 2),
Text( Text(
widget.tankerDetails.tanker_name, widget.tankerDetails.license_plate,
style: TextStyle( style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400),
fontSize: 16,
fontWeight: FontWeight.w700,
color: Colors.black,
), ),
],
), ),
SizedBox(height: 2), ),
PopupMenuButton<String>(
// 🔁 Use `child:` so you can place any widget (your 3-dots image)
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
child: Image.asset(
'images/popup_menu.png', // your 3-dots image
width: 22,
height: 22,
// If you want to tint it like an icon:
color: Color(0XFF939495), // remove if you want original colors
colorBlendMode: BlendMode.srcIn,
),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
offset: const Offset(0, 40),
color: Colors.white,
elevation: 4,
onSelected: (value) {
if (value == 'edit') {
openTankerSimpleSheet(context);
} else if (value == 'disable') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Disable selected')),
);
} else if (value == 'delete') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Delete selected')),
);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
children: [
Image.asset(
'images/edit.png',
width: 20,
height: 20,
color: Color(0XFF646566), // tint (optional)
colorBlendMode: BlendMode.srcIn,
),
const SizedBox(width: 12),
Text( Text(
widget.tankerDetails.type_of_water+' - '+widget.tankerDetails.capacity+' L', 'Edit',
style: TextStyle( style: fontTextStyle(14, const Color(0XFF646566), FontWeight.w400),
fontSize: 12, ),
color: Colors.black54, ],
), ),
), ),
PopupMenuItem(
value: 'disable',
child: Row(
children: [
Image.asset(
'images/disable.png',
width: 20,
height: 20,
color: Color(0XFF646566), // tint (optional)
colorBlendMode: BlendMode.srcIn,
),
const SizedBox(width: 12),
Text(
'Disable',
style: fontTextStyle(14, const Color(0XFF646566), FontWeight.w400),
),
], ],
), ),
), ),
PopupMenuItem(
value: 'delete',
child: Row(
children: [
Image.asset(
'images/delete.png',
width: 20,
height: 20,
color: Color(0XFFE2483D), // red like your example
colorBlendMode: BlendMode.srcIn,
),
const SizedBox(width: 12),
Text( Text(
widget.tankerDetails.license_plate, 'Delete',
style: TextStyle( style: fontTextStyle(14, const Color(0xFFE2483D), FontWeight.w400),
fontSize: 12, ),
color: Colors.black54, ],
fontWeight: FontWeight.w500,
), ),
), ),
], ],
)
],
), ),
const SizedBox(height: 24), const SizedBox(height: 12),
// 📍 Recent Trips Row(
const Text( children: [
"RECENT TRIPS", Expanded(
style: TextStyle( child: ElevatedButton(
fontSize: 12, style: ElevatedButton.styleFrom(
fontWeight: FontWeight.w600, backgroundColor: const Color(0xFFFFFFFF),
color: Colors.black87, foregroundColor: const Color(0xFF515253),
padding: const EdgeInsets.symmetric(vertical: 10),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: const BorderSide(color: Color(0xFF939495), width: 0.5),
),
// Or: side: const BorderSide(color: Color(0xFFC3C4C4), width: 1.2),
), ),
onPressed: () async {
// _showAssignTankerBottomSheet();
},
child: Text(
"Change Status",
style: fontTextStyle(14, const Color(0xFF515253), FontWeight.w500),
),
)
),
SizedBox(width: 8),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8270DB),
foregroundColor: const Color(0xFFFFFFFF),
padding: const EdgeInsets.symmetric(vertical: 10),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: const BorderSide(color: Color(0xFF8270DB), width: 0.5),
),
// Or: side: const BorderSide(color: Color(0xFFC3C4C4), width: 1.2),
),
onPressed: () async {
// _showAssignTankerBottomSheet();
},
child: Text(
"Assign",
style: fontTextStyle(14, const Color(0xFFFFFFFF), FontWeight.w500),
),
)
),
],
),
],
),
),
Padding(padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"RECENT TRIPS",
style: fontTextStyle(10, const Color(0xFF343637), FontWeight.w600),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@ -164,11 +718,15 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
from: "Bachupally Filling Station", from: "Bachupally Filling Station",
to: "Akriti Heights", to: "Akriti Heights",
), ),
const SizedBox(height: 30),
], ],
), ),
), ),
],
), ),
),
),
)
); );
} }
@ -278,4 +836,35 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
} }
// ====== Labeled Field Wrapper ======
class _LabeledField extends StatelessWidget {
final String label;
final Widget child;
const _LabeledField({required this.label, required this.child});
String _capFirstWord(String input) {
if (input.isEmpty) return input;
final i = input.indexOf(RegExp(r'\S'));
if (i == -1) return input;
return input.replaceRange(i, i + 1, input[i].toUpperCase());
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 14.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_capFirstWord(label),
style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w600),
),
const SizedBox(height: 6),
child,
],
),
);
}
}

@ -9,6 +9,10 @@ class TankersModel {
String status=''; String status='';
String license_plate=''; String license_plate='';
String supplier_name=''; String supplier_name='';
String manufacturing_year='';
String insurance_expiry='';
String tanker_type='';
String pumping_fee='';
List<dynamic> availability= []; List<dynamic> availability= [];
TankersModel(); TankersModel();
@ -25,7 +29,10 @@ class TankersModel {
rtvm.price = json['price'] ?? ''; rtvm.price = json['price'] ?? '';
rtvm.delivery_fee = json['delivery_fee'] ?? ''; rtvm.delivery_fee = json['delivery_fee'] ?? '';
rtvm.availability = json['availability'] ?? []; rtvm.availability = json['availability'] ?? [];
rtvm.manufacturing_year = json['manufacturing_year'] ?? '';
rtvm.insurance_expiry = json['insurance_exp_date'] ?? '';
rtvm.tanker_type = json['tanker_type'] ?? '';
rtvm.pumping_fee = json['pumping_fee'] ?? '';
return rtvm; return rtvm;
} }
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {

Loading…
Cancel
Save