From 37a5d3ce83acc198d6fc0680c06b72f8f3473c90 Mon Sep 17 00:00:00 2001 From: Sneha Date: Thu, 30 Oct 2025 11:04:36 +0530 Subject: [PATCH] changes --- images/delete.png | Bin 0 -> 1314 bytes images/disable.png | Bin 0 -> 2175 bytes images/popup_menu.png | Bin 0 -> 927 bytes .../{up_down arrow.png => up_down_arrow.png} | Bin lib/common/settings.dart | 39 +- lib/financials/financial_main_screen.dart | 2 +- lib/orders/accept_order_requests.dart | 4 +- lib/orders/all_orders.dart | 427 ++++++--- lib/orders/assign_driver.dart | 473 ++++----- lib/orders/change_driver.dart | 905 ++++++++++++++---- lib/orders/delivery_updates.dart | 131 +++ lib/orders/orders_model.dart | 4 + lib/resources/resources_drivers.dart | 2 +- lib/resources/resources_fleet.dart | 24 +- lib/resources/tanker_details.dart | 835 +++++++++++++--- lib/resources/tankers_model.dart | 9 +- 16 files changed, 2152 insertions(+), 703 deletions(-) create mode 100644 images/delete.png create mode 100644 images/disable.png create mode 100644 images/popup_menu.png rename images/{up_down arrow.png => up_down_arrow.png} (100%) create mode 100644 lib/orders/delivery_updates.dart diff --git a/images/delete.png b/images/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..7fe3e325bd2727704d4bdddd0ee37b56faf49aa7 GIT binary patch literal 1314 zcmV+-1>O3IP)@~0drDELIAGL9O(c600d`2O+f$vv5yP!O4vBf4CFH)Ahf)fNz5I6z$3BV^nK7m;UrLsT-+m)U131;ME zAG}i#-7norLkXv6(4Zb6oBpa)1vAql%|G4!-s|qygo;R850i+oN`&tKia}Ta>rZ(y zfHP*iS4y=Tzr8&{1rR)wwvt%jd{5sxK{d1`2G1LvKQeg4s}Ska<)&b9c*8e8Hmqxj zl5)*w?eHeVuqKdvSG5S2Fis^6mVDKW2%Zp7hf@>5p!=MN@4bR2ya@53wX&{XQH&`= z#Lcv|ybcf4;069Euaz&~ywNRUS6pG-V1nwxDD2W=&-!Jo@u#*NIzWm?E=csr z|5*}c)bkMJRE+uEiQ{-%e~vEx(Sclc-mjgQIOQ_>yx)1<9W9})$;Az?5NStgvGb;l z8K&1-N!-z-JX-9$ZM)6{hsqL!ummA2K?q9_!V-kA1R*Rz2ul#c5`?gLn&2rW{c*Vo z5;3B|$+&ZsW^zZ&%T=l4fyO89e-%_-BXrENCxmz>BD@lqOVh^>Hm@s_K6$XIMKF^_ zdwOvAu=jXn2NhGP5Ox@Fee{?f4cakpJFm=eR-L0O4^D*5-WUQ-LZw2mI~0bPKU>W$ zM6&5TJY0EG9W&QiifJw_7$H~X>nu+)d+Ls`52raPMB`{+8RBi7EpzIj0TvLuGkoa)mICjWerGt8EfpFr(!D zHb!CIX>lLHaJsc(hI2dwB4-i~1BEVnp%6^DsYuB*)-05z*Lt{}mw%#2?iZCU%mf~0 z`Tir*+smLQ=)ipbQAmWTTno5YRYwLo3 z&9U9FBq_R9LT+3aw2hS;{-L#Uh@3?qp8@T~&hf@|7mejm=`n(}&oRqqn4QxXTjN|i zmJr62N<@#CsFICtD&k1>#nQOD2$bG7q1XEUS>9d_GlBMfnCXQc!+2J%*bOkfbOfmd zp&w=@UD61k_@$!r-J4jki8s>t>*;b+v51+uIW)6G({aI>hn}x~8JQl%I{EgO$#x+6 Y7k@UWd17aAy#N3J07*qoM6N<$f(tugxBvhE literal 0 HcmV?d00001 diff --git a/images/disable.png b/images/disable.png new file mode 100644 index 0000000000000000000000000000000000000000..3d99f5fbc19d050727fdb52a31b6cc67f0feeb87 GIT binary patch literal 2175 zcmV-_2!QvAP)@~0drDELIAGL9O(c600d`2O+f$vv5yP?=%y0b!pEE>A9K=j%R$EL zCkS~1TrQ_nz|N$5F-U@${ek@>UzGsu%nrl1-90@$J;NYo92^|v`+d8_3@C75k0n4J z|8fl9@7I%3_JFY-ld{Y3T}t=_Ak5)#Xti3c9>fR+5u;o#=Qz*q7zTeTq}ah9oP)(- zNsw(M@T%Etnh-G}BdJoUl(6|@l#)V7#3+I7ck$J<2ZIxour5S^&`Emrs#@j-JH(F# zh#4qdttiL}jU({S&DGh9Uh^os_Qdu55{BwZsacq`7 z=s#OTs^%hzMl6(`OaIb?!?|Sd0eEX|ZPmI@Mo^FoVcT{IA5Izned_zh1*9r4Nu*vz zcIDcq?P+7@uGZGpT8j{)0}(bHiW=ka6#b~Y=M7TSk*L=Y5P?WSf(k|^d+nuOj6qil zzT0e`nGgdUvVHMW@LrYR+fBzVN`~mnxnu+Rs_+Vn+e$+m22HP7O z8-G}qrL<2U#WYp4HN2B5F9iwVXKE3|P)Ej1qlvNWyrrGPo|p zaPE27vJmA6ffIjMfb){`h5@RAZdAj7y9o7QqG%=|rTj-#md$4KT&HT%4v!>4+KFNsp|EXA5dy9Z zy>c$Q5HiwC)F?Y;7RW=6W2e_uh3IiAZ(0;-C5n>8RM%-~o#QgmV^!hp?QLY)WN9P{ z2QW?OmOs+2mPDO$w@eNvhXk=KL?ucRVHt?x>Uqm377Q^V?L^_|2A?4XNSY|lSzdLW zoJ<_u_}{8R43Wg17@9CqdG%${<6( zYh9QC=Wk_57_kt=jOUsHlV16^Z_;I3Xl{-dQ4-~3&}dDIFzMAHiAh6E5@V&LRc?%^ zh{BbklCr(gs5(r#V z1QWu%M9~9xOEa2I5P3k7TLJ+F%6Inm_BA;J$zdL%j7+8$B+Y0h=$0|qF6h3H%Zs$B z9U%x&lsu}-pwXon&E#?4+}!l^$>D638%Akk<0r~_zJ(NllG-HG;PgO^09;CR=K#tU7!cH2a!bJIn&qtNbenwGc!A4JJs z#xQi#9Em|Gpr;Klj*xob9`=aP~=9Fn)KO`f1m z7}=GNX&d!u6cf@TaENxuiy(rhBoUn5+u4UBVp?I?}^>ul*$MPh&2U2$lW) zBP8G$z~2!ZqeG+MO4`sCVqnfAVYI4Y+p>hKtrVz^@-+oNq0LdN*q5N!Bm%#zh+IHHD0&KRMSbRVRKNYoBK z-iX%kXvE_0q-0l79v%}ZNn_A~QD46NTS8UXK|@^#dQj9IXxWQlbOKBe+>Gl~omfax zfp%(Q>?g{09&2mny0~UUL8d002ovPDHLkV1o6M B`_=#e literal 0 HcmV?d00001 diff --git a/images/popup_menu.png b/images/popup_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f22c70e7d2580d67fe373bed9c2b6609a0b2f4 GIT binary patch literal 927 zcmV;Q17Q4#P)@~0drDELIAGL9O(c600d`2O+f$vv5yPgnAQ%F|#EnsqskNzJk-9K(%>5k#K@$jh zF&u};7$7-d|2?PiMvpjEy#W<-krCEfHh5&#NFf7E)b?x5M zS5Guu*JrJT5D;n;Sw$&&x4BPE2>^%)EX#6CH3@Sj1OSA9%e+pFtBwzmd54k4GuL zz~)*6B4BCE#8~&NecNpX9dbhg0K?W?tYvj0dIQ~Q+VAz_x?!}Ftb>@`w*=vN?N*EDnOl^xY)Ck)I z0EB?imX}F&%%*(+VQbnbni&7Q{jsc{Y?ub|VhQJR(QUzNV6)0tj?53evm*ikW*2sz zZmN#;&VD8kpc3i)_9Or7NqgIv5Pg#neQyZ_s0)_m{P%m>7Kx$n)xk@c-;7}(ZB2gi zIxEW)tyIFF!~Qaflp3R4=MV%z5ClOG1VPv@e*lMo4dG=ej#~f#002ovPDHLkV1n-f BnB4#X literal 0 HcmV?d00001 diff --git a/images/up_down arrow.png b/images/up_down_arrow.png similarity index 100% rename from images/up_down arrow.png rename to images/up_down_arrow.png diff --git a/lib/common/settings.dart b/lib/common/settings.dart index c7f28d8..d20bb57 100644 --- a/lib/common/settings.dart +++ b/lib/common/settings.dart @@ -153,10 +153,11 @@ class AppSettings{ static String uploadPicUrl = host + 'uploads-user'; static String getOrderRequestsFromUsersUrl = host + 'getuserRequestbookingsforsupplier'; 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 getTankersUrl = host + 'getTankers'; static String addTankerUrl = host + 'addTankers'; + static String updateTankerUrl = host + 'updateTankers'; static String getDriversUrl = host + 'getalldeliveryboys'; static String addDriversUrl = host + 'addDeliveryboys'; static String addSourceLocationsUrl = host + 'addSource'; @@ -164,7 +165,7 @@ class AppSettings{ static String setRatesDailyUrl = host + 'tankers'; static String getSupplierDetailsUrl = 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 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 getDrivers() async { var uri = Uri.parse(getDriversUrl+'/'+supplierId); //uri = uri.replace(query: 'supplierId=$supplierId'); diff --git a/lib/financials/financial_main_screen.dart b/lib/financials/financial_main_screen.dart index 36418f9..08f3313 100644 --- a/lib/financials/financial_main_screen.dart +++ b/lib/financials/financial_main_screen.dart @@ -364,7 +364,7 @@ class _FinancialMainScreenState extends State ), Image.asset("images/icon_tune.png", width: 24, height: 24), const SizedBox(width: 16), - Image.asset("images/up_down arrow.png", width: 24, height: 24), + Image.asset("images/up_down_arrow.png", width: 24, height: 24), ], ), ), diff --git a/lib/orders/accept_order_requests.dart b/lib/orders/accept_order_requests.dart index 34934ab..688ceed 100644 --- a/lib/orders/accept_order_requests.dart +++ b/lib/orders/accept_order_requests.dart @@ -367,7 +367,7 @@ class _AcceptOrderRequestsState extends State { payload["supplierId"] = AppSettings.supplierId; payload["amount"] = int.parse(widget.order.quoted_amount); payload["delivery_charges"] = advance; - payload["action"] = 'reject'; + payload["action"] = 'rejected'; bool status = await AppSettings.acceptOrderRequests( payload, widget.order.dbId); @@ -424,7 +424,7 @@ class _AcceptOrderRequestsState extends State { payload["supplierId"] = AppSettings.supplierId; payload["amount"] = int.parse(widget.order.quoted_amount); payload["delivery_charges"] = advance; - payload["action"] = 'accept'; + payload["action"] = 'accepted'; bool status = await AppSettings.acceptOrderRequests( payload, widget.order.dbId); diff --git a/lib/orders/all_orders.dart b/lib/orders/all_orders.dart index b894cf7..f067a8e 100644 --- a/lib/orders/all_orders.dart +++ b/lib/orders/all_orders.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.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/orders_model.dart'; import 'package:supplier_new/orders/search_order_appbar.dart'; @@ -9,6 +10,7 @@ import 'package:intl/intl.dart'; import 'assign_driver.dart'; import 'cancel_order.dart'; +import 'change_driver.dart'; class AllOrders extends StatefulWidget { final String navigationFrom; @@ -294,29 +296,42 @@ class OrderCard extends StatelessWidget { const OrderCard({super.key, required this.order, this.onRefresh}); 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": return Color(0XFFC4E8C3); - case "in-progress": - return Color(0XFFE8F2FF); + case "in_progress": + return Color(0XFFC9DFFE); case "cancelled": return Color(0XFFFCEDEC); case "assigned": return Color(0XFFF9DBC6); case "pending": return Color(0XFFFDF3D3); - case "advance_paid": - return Color(0XFFFDF3D3); default: return Colors.grey; } } 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": return Color(0XFF0A9E04); - case "in-progress": + case "in_progress": return Color(0XFF1D7AFC); case "cancelled": return Color(0XFFE2483D); @@ -324,8 +339,6 @@ class OrderCard extends StatelessWidget { return Color(0XFFE56910); case "pending": return Color(0XFFD0AE3C); - case "advance_paid": - return Color(0XFFD0AE3C); default: return Colors.grey; } @@ -336,171 +349,289 @@ class OrderCard extends StatelessWidget { final statusColor = _getStatusColor(); final textStatusColor = _getTextStatusColor(); - return Padding(padding: EdgeInsets.fromLTRB(0, 8, 0, 0), - child: Container( - margin: const EdgeInsets.only(bottom: 12), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 5, - offset: const Offset(0, 3), - ) - ], - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Image - ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(12), - bottomLeft: Radius.circular(0), - ), - child: order.imageAsset != '' - ? Image.asset( - order.imageAsset, - height: 145, - width: 145, - fit: BoxFit.cover, + String st = (order.status == 'advance_paid') ? 'pending' : order.status; + + return Padding( + padding: const EdgeInsets.fromLTRB(0, 8, 0, 0), + child: Container( + margin: const EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 5, + offset: const Offset(0, 3), ) - : Image.network( - order.imageAsset, - height: 100, - width: 100, - fit: BoxFit.cover, - ), - ), - - // Right side content - Expanded( - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Status chip - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 2, - ), - decoration: BoxDecoration( - color: statusColor, - borderRadius: BorderRadius.circular(4), - ), - child: Text( - order.status, - style: fontTextStyle(10,textStatusColor,FontWeight.w400) - ), - ), - const SizedBox(height: 6), + ], + ), - Text( - order.building_name, - style:fontTextStyle(16,Color(0XFF2D2E30),FontWeight.w600) - ), - Text( - order.displayAddress, - style: fontTextStyle(12,Color(0XFF646566),FontWeight.w400) + /// 👇 IntrinsicHeight allows both sides of the row to match height dynamically + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, // 👈 image stretches + children: [ + /// 🖼 Left Image + ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12), + ), + child: SizedBox( + width: 145, // fixed width + child: order.imageAsset.isNotEmpty + ? Image.asset( + order.imageAsset, + fit: BoxFit.cover, // 👈 fills height + ) + : Image.network( + order.imageAsset, + fit: BoxFit.cover, ), - const SizedBox(height: 4), + ), + ), - Text( - order.capacity+' - '+order.type_of_water, - style: fontTextStyle(14,Color(0XFF444444),FontWeight.w500) - ), - Text( - order.time+' , '+order.date, - style:fontTextStyle(8,Color(0XFF646566),FontWeight.w400) - ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + /// 📄 Right Content + Expanded( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Visibility( - visible: order.status.toLowerCase().toString()=='advance_paid', - child: GestureDetector( - onTap: ()async{ - final result = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AssignDriverScreen(order: order), - ), - ); + // Status chip + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: statusColor, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + st, + style: fontTextStyle(10, textStatusColor, FontWeight.w400), + ), + ), + const SizedBox(height: 6), - // If result indicates API reload - if (result == true) { - onRefresh?.call(); // 👈 safe call to refresh parent - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(22), - border: Border.all(color: const Color(0XFF939495)), + Text( + order.building_name, + style: fontTextStyle(16, const Color(0XFF2D2E30), FontWeight.w600), + ), + Text( + order.displayAddress, + style: fontTextStyle(12, const Color(0XFF646566), FontWeight.w400), + ), + const SizedBox(height: 4), + + Text( + '${order.capacity} - ${order.type_of_water}', + style: fontTextStyle(14, const Color(0XFF444444), FontWeight.w500), + ), + Text( + '${order.time} , ${order.date}', + style: fontTextStyle(8, const Color(0XFF646566), FontWeight.w400), + ), + const SizedBox(height: 12), + + /// =================== + /// 🟡 ASSIGN & CANCEL BUTTONS + /// =================== + Visibility( + visible: order.status.toLowerCase() == 'advance_paid', + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () async { + final result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AssignDriverScreen(order: order), + ), + ); + if (result == true) { + onRefresh?.call(); + } + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(22), + border: Border.all(color: const Color(0XFF939495)), + ), + child: const Padding( + padding: EdgeInsets.fromLTRB(8, 4, 8, 4), + child: Text( + "Assign", + style: TextStyle( + fontSize: 14, + color: Color(0XFF515253), + fontWeight: FontWeight.w400), + ), + ), ), - child: Padding( - padding: EdgeInsets.fromLTRB(8,4,8,4), - child: Text( - "Assign", - style: fontTextStyle( - 14, const Color(0XFF515253), 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( - visible: order.status.toLowerCase().toString()=='advance_paid', + visible: order.status.toLowerCase() == 'in_progress', child: GestureDetector( - onTap: ()async{ + onTap: () async { final result = await Navigator.push( context, MaterialPageRoute( - builder: (context) => CancelOrderScreen(order: order), + builder: (context) => AssignDriverScreen(order: order), ), ); - - // If result indicates API reload if (result == true) { - onRefresh?.call(); // 👈 safe call to refresh parent + onRefresh?.call(); } }, child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(22), - border: Border.all(color: const Color(0XFFE2483D)), - color: Color(0XFFE2483D) + 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( + "Track Delivery", + style: TextStyle( + fontSize: 14, + color: Colors.white, + fontWeight: FontWeight.w400), ), - child: Padding( - padding: EdgeInsets.fromLTRB(8,4,8,4), - child: Text( - "Cancel", - style: fontTextStyle( - 14, const Color(0XFFFFFFFF), 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( + 8, const Color(0xFF646566), FontWeight.w400), + ), + ), + ], + ), + + ], + ), + ), - /*Text( - order.extraInfo, - style:fontTextStyle(12, order.status == "in-progress" - ? Color(0XFF1D7AFC) - : Color(0XFF646566),FontWeight.w400) + ], + ), + SizedBox(height: MediaQuery.of(context).size.height * .004), + GestureDetector( + 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), + ), + ), + ), + ), + ], + ) + ), - ),*/ - ], + ], + ), + ), ), - ), + ], ), - ], + ), ), - ),); + ); } } \ No newline at end of file diff --git a/lib/orders/assign_driver.dart b/lib/orders/assign_driver.dart index 6ceb831..af0b625 100644 --- a/lib/orders/assign_driver.dart +++ b/lib/orders/assign_driver.dart @@ -129,8 +129,8 @@ class _AssignDriverScreenState extends State { children: [ Text( "Assign Tanker", - style: fontTextStyle( - 16, const Color(0XFF2A2A2A), FontWeight.w600), + style: fontTextStyle(16, + const Color(0XFF2A2A2A), FontWeight.w600), ), GestureDetector( onTap: () => Navigator.pop(context), @@ -157,7 +157,8 @@ class _AssignDriverScreenState extends State { child: Row( children: [ Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Text( widget.order.building_name, @@ -197,70 +198,75 @@ class _AssignDriverScreenState extends State { const SizedBox(height: 12), isTankersDataLoading - ? const Center(child: CircularProgressIndicator()) + ? const Center( + child: CircularProgressIndicator()) : (tankersList.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: tankersList - .where((t) => - _capToLiters(t.capacity) == - _capToLiters(widget.order.capacity)) - .length, - separatorBuilder: (_, __) => - const SizedBox(height: 12), - itemBuilder: (context, idx) { - final filteredTankers = tankersList - .where((t) => - _capToLiters(t.capacity) == - _capToLiters(widget.order.capacity)) - .toList(); - final d = filteredTankers[idx]; - final isSelected = - selectedTankerIndex == idx; + ? 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: tankersList + .where((t) => + _capToLiters(t.capacity) == + _capToLiters( + widget.order.capacity)) + .length, + separatorBuilder: (_, __) => + const SizedBox(height: 12), + itemBuilder: (context, idx) { + final filteredTankers = tankersList + .where((t) => + _capToLiters(t.capacity) == + _capToLiters( + widget.order.capacity)) + .toList(); + final d = filteredTankers[idx]; + final isSelected = + selectedTankerIndex == idx; - return GestureDetector( - onTap: () { - setModalState(() { - selectedTankerIndex = idx; - selectedDriverIndex = null; // reset driver selection if tanker changes - }); - }, - child: Card( - elevation: 1, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12), - side: BorderSide( - color: isSelected - ? primaryColor - : const Color(0XFFC3C4C4), - width: 1, - ), - ), - child: TankersCard( - title: d.tanker_name, - subtitle: d.type_of_water, - capacity: d.capacity, - code: d.license_plate, - owner: d.supplier_name, - status: List.from( - d.availability), - ), - ), - ); - }, - )), + return GestureDetector( + onTap: () { + setModalState(() { + selectedTankerIndex = idx; + selectedDriverIndex = + null; // reset driver selection if tanker changes + }); + }, + child: Card( + elevation: 1, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(12), + side: BorderSide( + color: isSelected + ? primaryColor + : const Color(0XFFC3C4C4), + width: 1, + ), + ), + child: TankersCard( + title: d.tanker_name, + subtitle: d.type_of_water, + capacity: d.capacity, + code: d.license_plate, + owner: d.supplier_name, + status: List.from( + d.availability), + ), + ), + ); + }, + )), const SizedBox(height: 8), // 🧍 Driver List @@ -274,59 +280,70 @@ class _AssignDriverScreenState extends State { // 🚨 Driver list disabled until tanker is selected selectedTankerIndex == null ? Container( - width: double.infinity, - 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), - ), - ), - ) + width: double.infinity, + 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), + ), + ), + ) : isLoading - ? const Center(child: CircularProgressIndicator()) - : (driversList.isEmpty - ? Center( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: Text( - 'No Data Available', - style: fontTextStyle( - 12, - const Color(0xFF939495), - FontWeight.w500), - ), - ), - ) - : ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - itemCount: driversList.length, - separatorBuilder: (_, __) => const SizedBox(height: 12), - itemBuilder: (context, idx) { - final d = driversList[idx]; - final isSelected = selectedDriverIndex == idx; - final isAvailable = d.status == "available"; + ? const Center( + child: CircularProgressIndicator()) + : (driversList.isEmpty + ? Center( + child: Padding( + padding: + const EdgeInsets.symmetric( + vertical: 12), + child: Text( + 'No Data Available', + style: fontTextStyle( + 12, + const Color(0xFF939495), + FontWeight.w500), + ), + ), + ) + : ListView.separated( + shrinkWrap: true, + physics: + const NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: driversList.length, + separatorBuilder: (_, __) => + const SizedBox(height: 12), + itemBuilder: (context, idx) { + final d = driversList[idx]; + final isSelected = + selectedDriverIndex == idx; + final isAvailable = + d.status == "available"; - final statusColor = isAvailable - ? const Color(0XFF0A9E04) - : (d.status == "on delivery" - ? const Color(0XFFD0AE3C) - : (d.status == "offline" - ? const Color(0XFF939495) - : Colors.grey)); + final statusColor = isAvailable + ? const Color(0XFF0A9E04) + : (d.status == "on delivery" + ? const Color(0XFFD0AE3C) + : (d.status == "offline" + ? const Color( + 0XFF939495) + : Colors.grey)); - return GestureDetector( - onTap: () { - /* if (isAvailable) { + return GestureDetector( + onTap: () { + /* if (isAvailable) { setModalState(() { selectedDriverIndex = idx; }); @@ -335,57 +352,86 @@ class _AssignDriverScreenState extends State { 'Only available drivers can be selected', ); }*/ - setModalState(() { - selectedDriverIndex = idx; - }); - }, - child: Card( - color: const Color(0XFFFFFFFF), - elevation: 1, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide( - color: isSelected ? primaryColor : const Color(0XFFC3C4C4), - width: 1, - ), - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - children: [ - Image.asset( - 'images/avatar.png', - fit: BoxFit.cover, - width: 20, - height: 20, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - d.driver_name, - style: fontTextStyle( - 14, const Color(0XFF2D2E30), FontWeight.w500), - ), - ), - const SizedBox(width: 8), - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all(color: statusColor), - ), - child: Text( - d.status, - style: fontTextStyle(10, statusColor, FontWeight.w400), - ), - ), - ], - ), - ), - ), - ); - }, - )), + setModalState(() { + selectedDriverIndex = idx; + }); + }, + child: Card( + color: + const Color(0XFFFFFFFF), + elevation: 1, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular( + 12), + side: BorderSide( + color: isSelected + ? primaryColor + : const Color( + 0XFFC3C4C4), + width: 1, + ), + ), + child: Padding( + padding: + const EdgeInsets.all( + 12.0), + child: Row( + children: [ + Image.asset( + 'images/avatar.png', + fit: BoxFit.cover, + width: 20, + height: 20, + ), + const SizedBox( + width: 8), + Expanded( + child: Text( + d.driver_name, + style: fontTextStyle( + 14, + const Color( + 0XFF2D2E30), + FontWeight + .w500), + ), + ), + const SizedBox( + width: 8), + Container( + padding: + const EdgeInsets + .symmetric( + horizontal: 6, + vertical: 2), + decoration: + BoxDecoration( + borderRadius: + BorderRadius + .circular( + 4), + border: Border.all( + color: + statusColor), + ), + child: Text( + d.status, + style: + fontTextStyle( + 10, + statusColor, + FontWeight + .w400), + ), + ), + ], + ), + ), + ), + ); + }, + )), ], ), ), @@ -395,76 +441,73 @@ class _AssignDriverScreenState extends State { SafeArea( top: false, child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), child: SizedBox( width: double.infinity, child: GestureDetector( onTap: () async { if (selectedTankerIndex == null) { - AppSettings.longFailedToast('Please select tanker'); + AppSettings.longFailedToast( + 'Please select tanker'); return; } - /*if (selectedDriverIndex == null) { - AppSettings.longFailedToast('Please select driver'); - return; - }*/ final filteredTankers = tankersList .where((t) => - _capToLiters(t.capacity) == - _capToLiters(widget.order.capacity)) + _capToLiters(t.capacity) == + _capToLiters(widget.order.capacity)) .toList(); - final selectedTanker = filteredTankers[selectedTankerIndex!]; - final selectedDriver = driversList[selectedDriverIndex!]; + final selectedTanker = + filteredTankers[selectedTankerIndex!]; + final selectedDriver = selectedDriverIndex != null + ? driversList[selectedDriverIndex!] + : null; AppSettings.preLoaderDialog(context); - - bool isOnline = await AppSettings.internetConnectivity(); + bool isOnline = + await AppSettings.internetConnectivity(); if (isOnline) { - var payload = new Map(); - payload["tankerName"] = selectedTanker.tanker_name; - if(selectedDriverIndex != null){ - payload["delivery_agent"] = selectedDriver.driver_name; - payload["delivery_agent_mobile"] = selectedDriver.phone_number; - } - else{ - payload["delivery_agent"] =null; - payload["delivery_agent_mobile"] = null; - } + var payload = {}; + 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); - bool status = await AppSettings.assignTanker(payload, widget.order.dbId); + Navigator.of(context, rootNavigator: true) + .pop(); // close loader - try { - if (status) { - Navigator.of(context,rootNavigator: true).pop(); - AppSettings.longFailedToast("Tanker assigned successfully"); - Navigator.pop(context, true); + if (status) { + AppSettings.longSuccessToast( + "Tanker assigned successfully"); - } - else{ - Navigator.of(context,rootNavigator: true).pop(); - AppSettings.longFailedToast("Failed to assign tanker"); - } - } catch (e) { - Navigator.of(context,rootNavigator: true).pop(); - print(e); + // 👇 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 { + Navigator.of(context, rootNavigator: true) + .pop(); + AppSettings.longFailedToast( + "Please Check internet"); } - else{ - Navigator.of(context,rootNavigator: true).pop(); - AppSettings.longFailedToast("Please Check internet"); - } - - if (context.mounted) Navigator.pop(context); }, child: Container( decoration: BoxDecoration( color: const Color(0XFF8270DB), - borderRadius: BorderRadius.circular(24), + borderRadius : BorderRadius.circular(24), ), alignment: Alignment.center, padding: const EdgeInsets.symmetric(vertical: 12), @@ -954,7 +997,8 @@ class TankersCard extends StatelessWidget { borderRadius: BorderRadius.circular(8), 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", maxLines: 1, overflow: TextOverflow.ellipsis, - style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500), + style: fontTextStyle( + 14, const Color(0xFF2D2E30), FontWeight.w500), ), ), const SizedBox(width: 8), @@ -1009,7 +1054,8 @@ class TankersCard extends StatelessWidget { ), Text( 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 { ], ), ); - } } diff --git a/lib/orders/change_driver.dart b/lib/orders/change_driver.dart index 53e8429..f151485 100644 --- a/lib/orders/change_driver.dart +++ b/lib/orders/change_driver.dart @@ -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 ChangeDriverScreen extends StatefulWidget { var order; @@ -20,12 +18,18 @@ class _ChangeDriverScreenState extends State { double amountToPayAfterDelivery = 0.0; double totalFare = 0.0; bool isLoading = false; + bool isTankersDataLoading = false; List driversList = []; + List tankersList = []; + TankersModel? orderTanker; + DriversModel? orderDriver; + @override void initState() { // TODO: implement initState super.initState(); + _fetchTankers(); _fetchDrivers(); advance = 150; advancePayable = advance; @@ -41,8 +45,21 @@ class _ChangeDriverScreenState extends State { .map((e) => DriversModel.fromJson(e)) .toList(); 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(() { driversList = data; + orderDriver = matched; // store matched driver or null isLoading = false; }); } catch (e) { @@ -51,153 +68,489 @@ class _ChangeDriverScreenState extends State { } } - void _showAssignDriverBottomSheet() { - int? selectedDriverIndex; // ✅ Track selected driver + Future _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; + + // 🔎 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? 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( + backgroundColor: const Color(0XFFFFFFFF), context: context, isScrollControlled: true, isDismissible: false, - enableDrag: false, + enableDrag: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (context) { - return WillPopScope( // ✅ block Android back button + 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) { - 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, + return FractionallySizedBox( + heightFactor: 0.95, + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(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( + "Edit Order", + 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), + + /// 🛻 Tanker List Text( - "Assign Driver", - style: fontTextStyle(16, const Color(0XFF2A2A2A), FontWeight.w600), + "SELECTED TANKER", + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), ), - GestureDetector( - onTap: (){ - Navigator.pop(context); - }, - child: Image.asset('images/cross.png', height: 24, width: 24,color: Color(0XFF2A2A2A),), + const SizedBox(height: 12), + 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), + ), ) - ], - ), - 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), + : 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, + ), + ), + child: TankersCard( + title: t.tanker_name, + subtitle: t.type_of_water, + capacity: t.capacity, + code: t.license_plate, + owner: t.supplier_name, + status: List.from( + t.availability), + ), ), - ], + ); + }, + )), + + const SizedBox(height: 16), + + /// 🧍 Driver List + Text( + "SELECTED DRIVER", + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), + ), + const SizedBox(height: 12), + + selectedTankerIndex == null + ? Container( + width: double.infinity, + 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)), ), - const Spacer(), - Text( - '${widget.order.distanceInKm} Km', - style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400), + child: Center( + child: Text( + 'Select a tanker to choose driver', + style: fontTextStyle( + 14, + const Color(0xFF2D2E30), + FontWeight.w400), + ), ), - ], - ), - ), - const SizedBox(height: 16), - - Text( - "SELECT DRIVER", - style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600), - ), - const SizedBox(height: 12), + ) + : ListView.separated( + shrinkWrap: true, + physics: + const NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: driversList.length, + separatorBuilder: (_, __) => + const SizedBox(height: 12), + itemBuilder: (context, idx) { + final d = driversList[idx]; + final isSelected = + selectedDriverIndex == idx; + final statusColor = d.status == "available" + ? const Color(0XFF0A9E04) + : (d.status == "on delivery" + ? const Color(0XFFD0AE3C) + : (d.status == "offline" + ? const Color(0XFF939495) + : Colors.grey)); - // 🧍 Driver List - Expanded( - child: isLoading - ? const Center(child: CircularProgressIndicator()) - : ListView.separated( - controller: scrollController, - itemCount: driversList.length, - separatorBuilder: (_, __) => const SizedBox(height: 12), - itemBuilder: (context, idx) { - final d = driversList[idx]; - final bool isSelected = selectedDriverIndex == idx; - final bool isAvailable = d.status == "available"; - - final statusColor = isAvailable - ? const Color(0XFF0A9E04) - : (d.status == "on delivery" - ? const Color(0XFFD0AE3C) - : (d.status == "offline" - ? const Color(0XFF939495) - : Colors.grey)); - - return GestureDetector( - onTap: () { - if (isAvailable) { // ✅ only selectable if available + return GestureDetector( + onTap: () { setModalState(() { selectedDriverIndex = idx; }); - } else { - AppSettings.longFailedToast( - 'Only available drivers can be selected', - ); - } - }, - child: Opacity( - opacity: isAvailable ? 1 : 1, // 👈 grey out non-available + }, child: Card( + color: const Color(0XFFFFFFFF), + elevation: 1, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + borderRadius: + BorderRadius.circular(12), side: BorderSide( color: isSelected - ? const Color(0XFF8270DB) + ? primaryColor : const Color(0XFFC3C4C4), width: 1, ), ), - color: isSelected - ? const Color(0XFFEDEBFF) - : Colors.white, child: Padding( padding: const EdgeInsets.all(12.0), child: Row( @@ -208,64 +561,103 @@ class _ChangeDriverScreenState extends State { width: 20, height: 20, ), - SizedBox( - width: MediaQuery.of(context).size.width * .016), + const SizedBox(width: 8), Expanded( child: Text( d.driver_name, style: fontTextStyle( - 14, - const Color(0XFF2D2E30), - FontWeight.w500, - ), + 14, + const Color( + 0XFF2D2E30), + FontWeight.w500), ), ), - SizedBox( - width: MediaQuery.of(context).size.width * .016), + const SizedBox(width: 8), Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, vertical: 2), + padding: + const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all(color: statusColor), + borderRadius: + BorderRadius.circular(4), + border: Border.all( + color: statusColor), ), child: Text( d.status, style: fontTextStyle( - 10, - statusColor, - FontWeight.w400, - ), + 10, + statusColor, + FontWeight.w400), ), ), ], ), ), ), - ), - ); - }, - ), + ); + }, + ), + ], ), + ), + ), - const SizedBox(height: 16), - - // 🟣 Assign Button - SizedBox( + /// ✅ Assign Button + SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + child: SizedBox( width: double.infinity, child: GestureDetector( onTap: () async { - if (selectedDriverIndex == null) { - AppSettings.longFailedToast('Please select driver'); + if (selectedTankerIndex == null) { + AppSettings.longFailedToast( + 'Please select tanker'); return; } - final selectedDriver = driversList[selectedDriverIndex!]; + final selectedTanker = + filteredTankers[selectedTankerIndex!]; + final selectedDriver = selectedDriverIndex != null + ? driversList[selectedDriverIndex!] + : null; + + AppSettings.preLoaderDialog(context); + bool isOnline = + await AppSettings.internetConnectivity(); - // ✅ Call your API here - // await _assignDriverApi(selectedDriver.driver_id); + if (isOnline) { + var payload = {}; + payload["tankerName"] = + selectedTanker.tanker_name; + payload["delivery_agent"] = + selectedDriver?.driver_name; + payload["delivery_agent_mobile"] = + selectedDriver?.phone_number; - if (context.mounted) Navigator.pop(context); + 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( decoration: BoxDecoration( @@ -277,29 +669,26 @@ class _ChangeDriverScreenState extends State { child: Text( 'Assign', style: fontTextStyle( - 14, const Color(0XFFFFFFFF), FontWeight.w500), + 14, Colors.white, FontWeight.w500), ), ), ), ), - ], + ), ), - ); - }, + ], + ), ); }, - ),); + ), + ); }, ); } - - - @override Widget build(BuildContext context) { - return Scaffold( backgroundColor: Colors.white, extendBodyBehindAppBar: true, @@ -523,42 +912,30 @@ class _ChangeDriverScreenState extends State { style: fontTextStyle( 12, const Color(0XFF646566), FontWeight.w400), ), - Image.asset( - 'images/avatar.png', - fit: BoxFit.cover, - width: 20, - height: 20, + SizedBox( + height: MediaQuery.of(context).size.height * .032, + ), + Text( + "ASSIGNED TANKER", + style: fontTextStyle( + 10, const Color(0XFF646566), FontWeight.w600), ), SizedBox( - width: MediaQuery.of(context).size.width * .016), - Expanded( - child: Text( - 'd.driver_name', - style: fontTextStyle( - 14, - const Color(0XFF2D2E30), - FontWeight.w500, - ), - ), + height: MediaQuery.of(context).size.height * .012, ), + _assignedTankerDetails(), SizedBox( - width: MediaQuery.of(context).size.width * .016), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, vertical: 2), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all(color: Colors.green), - ), - child: Text( - 'd.status', - style: fontTextStyle( - 10, - Colors.green, - FontWeight.w400, - ), - ), + height: MediaQuery.of(context).size.height * .012, + ), + Text( + "ASSIGNED TO", + style: fontTextStyle( + 10, const Color(0XFF646566), FontWeight.w600), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .012, ), + _assignedDriverDetails() ], )), ], @@ -583,7 +960,7 @@ class _ChangeDriverScreenState extends State { padding: EdgeInsets.symmetric(vertical: 10), ), onPressed: () async { - AppSettings.preLoaderDialog(context); + /*AppSettings.preLoaderDialog(context); bool isOnline = await AppSettings.internetConnectivity(); @@ -615,7 +992,8 @@ class _ChangeDriverScreenState extends State { } else { Navigator.of(context, rootNavigator: true).pop(); AppSettings.longFailedToast("Please Check internet"); - } + }*/ + Navigator.pop(context); }, child: Text( "CANCEL", @@ -633,10 +1011,10 @@ class _ChangeDriverScreenState extends State { padding: EdgeInsets.symmetric(vertical: 10), ), onPressed: () async { - _showAssignDriverBottomSheet(); + _showAssignTankerBottomSheet(); }, child: Text( - "Change Order", + "Edit Order", style: fontTextStyle( 14, const Color(0XFFFFFFFF), FontWeight.w400), ), @@ -728,3 +1106,144 @@ class _ChangeDriverScreenState extends State { ); } } + +// ====== TankersCard ====== +class TankersCard extends StatelessWidget { + final String title; + final String subtitle; + final String capacity; + final String code; + final String owner; + final List 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), + ), + ], + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/orders/delivery_updates.dart b/lib/orders/delivery_updates.dart new file mode 100644 index 0000000..4febc60 --- /dev/null +++ b/lib/orders/delivery_updates.dart @@ -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 createState() => _DeliveryUpdatesPageState(); +} + +class _DeliveryUpdatesPageState extends State { + List 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), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/orders/orders_model.dart b/lib/orders/orders_model.dart index 61bf0cf..3aa2744 100644 --- a/lib/orders/orders_model.dart +++ b/lib/orders/orders_model.dart @@ -21,6 +21,8 @@ class OrdersModel { String status=''; String date=''; String imageAsset='images/building.png'; + String delivery_agent_name = ''; + String tanker_name = ''; OrdersModel(); @@ -39,6 +41,8 @@ class OrdersModel { rtvm.quoted_amount = json['price'].toString() ?? ''; rtvm.lng=json['longitude'] ?? 0.0; rtvm.lat=json['latitude'] ?? 0.0; + rtvm.delivery_agent_name = json['delivery_agent'] ?? ''; + rtvm.tanker_name = json['tankerName'] ?? ''; // Split and trim List parts = rtvm.address.split(',').map((e) => e.trim()).toList(); diff --git a/lib/resources/resources_drivers.dart b/lib/resources/resources_drivers.dart index 71c17ef..8913533 100644 --- a/lib/resources/resources_drivers.dart +++ b/lib/resources/resources_drivers.dart @@ -509,7 +509,7 @@ class _ResourcesDriverScreenState extends State { const SizedBox(width: 16), Image.asset("images/icon_tune.png", width: 24, height: 24), const SizedBox(width: 16), - Image.asset("images/up_down arrow.png", width: 24, height: 24), + Image.asset("images/up_down_arrow.png", width: 24, height: 24), ], ), const SizedBox(height: 12), diff --git a/lib/resources/resources_fleet.dart b/lib/resources/resources_fleet.dart index 2830477..86c01c4 100644 --- a/lib/resources/resources_fleet.dart +++ b/lib/resources/resources_fleet.dart @@ -36,7 +36,6 @@ class FirstCharUppercaseFormatter extends TextInputFormatter { ); } } -void main() => runApp(const MaterialApp(home: ResourcesFleetScreen())); class ResourcesFleetScreen extends StatefulWidget { const ResourcesFleetScreen({super.key}); @@ -570,13 +569,16 @@ class _ResourcesFleetScreenState extends State { itemBuilder: (context, idx) { final it = filtered[idx]; return GestureDetector( - onTap: (){ - Navigator.push( + onTap: () async{ + final result = await Navigator.push( context, MaterialPageRoute( builder: (context) => TankerDetailsPage(tankerDetails: it), ), ); + if (result == true) { + _fetchTankers(); + } }, child: TankCard( title: it.tanker_name, @@ -735,21 +737,7 @@ class TankCard extends StatelessWidget { const SizedBox(height: 10), Row( children: [ - ClipOval( - 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, - ), - ), - ), - ), + Image.asset('images/avatar.png', width: 12, height: 12), const SizedBox(width: 6), Expanded( child: Text(owner, style: fontTextStyle(8, const Color(0xFF646566), FontWeight.w400)), diff --git a/lib/resources/tanker_details.dart b/lib/resources/tanker_details.dart index 1a02634..fef1426 100644 --- a/lib/resources/tanker_details.dart +++ b/lib/resources/tanker_details.dart @@ -1,4 +1,36 @@ 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 { var tankerDetails; @@ -9,61 +41,451 @@ class TankerDetailsPage extends StatefulWidget { } class _TankerDetailsPageState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - appBar: AppBar( - backgroundColor: Colors.white, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios, color: Colors.black), - onPressed: () => Navigator.pop(context), - ), - title: Text( - widget.tankerDetails.tanker_name.isNotEmpty - ? widget.tankerDetails.tanker_name[0].toUpperCase() + - widget.tankerDetails.tanker_name.substring(1) - : '', - style: const TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.w600, + + final _formKey = GlobalKey(); + final _nameCtrl = TextEditingController(); + final _capacityCtrl = TextEditingController(); + final _plateCtrl = TextEditingController(); + final _mfgYearCtrl = TextEditingController(); + final _insExpiryCtrl = TextEditingController(); + + // Dropdown selections (sheet) + String? selectedType; + String? selectedTypeOfWater; + + // Dropdown options (adjust to your backend) + final List tankerTypes = const [ + "Standard Tanker", + "High-Capacity Tanker", + "Small Tanker", + ]; + final List 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 _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), + ), ), - ), - centerTitle: false, - actions: [ - TextButton( - onPressed: () {}, - child: const Text( - "HELP", - style: TextStyle( - color: Color(0xFF4F46E5), - fontWeight: FontWeight.w600, - fontSize: 14, + 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 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 _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 = { + "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 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)), + ), + child: Padding( + padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + viewInsets), + child: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + 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), + ), + ), + ), + ), + ], + ), + + _LabeledField( + label: "Tanker Name *", + child: TextFormField( + controller: _nameCtrl, + 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( + 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( + 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( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 🛻 Tanker Image - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Stack( + 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: [ - Image.asset( - 'images/tanker_image.jpeg', // Replace with your image - width: double.infinity, - height: 180, - fit: BoxFit.cover, - ), - /*Positioned( + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Stack( + children: [ + Image.asset( + 'images/tanker_image.jpeg', // Replace with your image + width: double.infinity, + height: 180, + fit: BoxFit.cover, + ), + /*Positioned( bottom: 12, left: 12, child: Row( @@ -74,101 +496,237 @@ class _TankerDetailsPageState extends State { ], ), ),*/ - ], - ), - ), - const SizedBox(height: 16), - Row( - children: [ - _buildStatusChip("filled", const Color(0xFF4F46E5)), - const SizedBox(width: 8), - _buildStatusChip("available", const Color(0xFF0A9E04)), - ], - ), - const SizedBox(height: 16), - - // 🚚 Tanker Info - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const CircleAvatar( - radius: 18, - backgroundImage: AssetImage('images/avatar.png'), - ), - const SizedBox(width: 12), - Expanded( - child: Column( + ], + ), + ), + const SizedBox(height: 16), + Row( + children: widget.tankerDetails.availability + .map((s) { + final chipTextColor = _chipTextColor(s); + 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), + // 🚚 Tanker Info + Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Ramesh Krishna", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.black, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.tankerDetails.tanker_name, + style: fontTextStyle(16, const Color(0xFF2A2A2A), FontWeight.w600), + ), + SizedBox(height: 2), + Text( + widget.tankerDetails.type_of_water+' - '+widget.tankerDetails.capacity+' L', + style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400), + ), + SizedBox(height: 2), + Text( + widget.tankerDetails.license_plate, + style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400), + ), + ], ), ), - SizedBox(height: 2), - Text( - widget.tankerDetails.tanker_name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - color: Colors.black, + + PopupMenuButton( + // 🔁 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( + 'Edit', + style: fontTextStyle(14, const Color(0XFF646566), FontWeight.w400), + ), + ], + ), + ), + 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( + 'Delete', + style: fontTextStyle(14, const Color(0xFFE2483D), FontWeight.w400), + ), + ], + ), + ), + ], + ) + + ], + ), + const SizedBox(height: 12), + + Row( + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFFFFFFF), + 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(height: 2), - Text( - widget.tankerDetails.type_of_water+' - '+widget.tankerDetails.capacity+' L', - style: TextStyle( - fontSize: 12, - color: Colors.black54, - ), + 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), + ), + ) ), ], ), - ), - Text( - widget.tankerDetails.license_plate, - style: TextStyle( - fontSize: 12, - color: Colors.black54, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - const SizedBox(height: 24), - - // 📍 Recent Trips - const Text( - "RECENT TRIPS", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Colors.black87, + ], ), ), - const SizedBox(height: 12), - _buildTripCard( - driverName: "Ramesh Krishna", - time: "7:02 PM, 28 Jun 2025", - from: "Bachupally Filling Station", - to: "Akriti Heights", - ), - const SizedBox(height: 8), - _buildTripCard( - driverName: "Ramesh Krishna", - time: "12:44 PM, 26 Jun 2025", - from: "Bachupally Filling Station", - to: "Akriti Heights", + 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), + + _buildTripCard( + driverName: "Ramesh Krishna", + time: "7:02 PM, 28 Jun 2025", + from: "Bachupally Filling Station", + to: "Akriti Heights", + ), + const SizedBox(height: 8), + _buildTripCard( + driverName: "Ramesh Krishna", + time: "12:44 PM, 26 Jun 2025", + from: "Bachupally Filling Station", + to: "Akriti Heights", + ), + ], + ), ), - const SizedBox(height: 30), + ], ), ), ), + ) ); } @@ -278,4 +836,35 @@ class _TankerDetailsPageState extends State { } +// ====== 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, + ], + ), + ); + } +} diff --git a/lib/resources/tankers_model.dart b/lib/resources/tankers_model.dart index f8261e7..3a0b4f5 100644 --- a/lib/resources/tankers_model.dart +++ b/lib/resources/tankers_model.dart @@ -9,6 +9,10 @@ class TankersModel { String status=''; String license_plate=''; String supplier_name=''; + String manufacturing_year=''; + String insurance_expiry=''; + String tanker_type=''; + String pumping_fee=''; List availability= []; TankersModel(); @@ -25,7 +29,10 @@ class TankersModel { rtvm.price = json['price'] ?? ''; rtvm.delivery_fee = json['delivery_fee'] ?? ''; 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; } Map toJson() => {