diff --git a/images/avatar.png b/images/avatar.png new file mode 100644 index 0000000..f1e3f2c Binary files /dev/null and b/images/avatar.png differ diff --git a/images/license_bg.png b/images/license_bg.png new file mode 100644 index 0000000..49ed00c Binary files /dev/null and b/images/license_bg.png differ diff --git a/images/profile_user.png b/images/profile_user.png new file mode 100644 index 0000000..65c0b55 Binary files /dev/null and b/images/profile_user.png differ diff --git a/images/tanker_image.jpeg b/images/tanker_image.jpeg new file mode 100644 index 0000000..677cea4 Binary files /dev/null and b/images/tanker_image.jpeg differ diff --git a/lib/common/calander.dart b/lib/common/calander.dart new file mode 100644 index 0000000..2ecd67b --- /dev/null +++ b/lib/common/calander.dart @@ -0,0 +1,206 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; +import 'package:supplier_new/common/settings.dart'; + +class DeliveryCalendarScreen extends StatefulWidget { + const DeliveryCalendarScreen({super.key}); + + @override + State createState() => _DeliveryCalendarScreenState(); +} + +class _DeliveryCalendarScreenState extends State { + DateTime _focusedMonth = DateTime(2025, 10); + late List> _calendarData; + + @override + void initState() { + super.initState(); + _calendarData = _generateCalendarData(); + } + + List> _generateCalendarData() { + return [ + {"day": 1, "status": "Delivered"}, + {"day": 2, "status": "Delivered"}, + {"day": 3, "status": "Rescheduled"}, + {"day": 4, "status": "Delivered"}, + {"day": 5, "status": "Delivered"}, + {"day": 6, "status": "Delivered"}, + {"day": 7, "status": "Cancelled"}, + {"day": 8, "status": "Delivered"}, + {"day": 9, "status": "Delivered"}, + {"day": 10, "status": "Delivered"}, + {"day": 11, "status": "Cancelled"}, + {"day": 12, "status": "Delivery"}, + {"day": 13, "status": "Delivery"}, + {"day": 14, "status": "Delivery"}, + {"day": 15, "status": "Delivery"}, + {"day": 16, "status": "Delivery"}, + {"day": 17, "status": "Delivery"}, + {"day": 18, "status": "Delivery"}, + {"day": 19, "status": "Delivery"}, + {"day": 20, "status": "Delivery"}, + {"day": 21, "status": "Delivery"}, + {"day": 22, "status": "Delivery"}, + {"day": 23, "status": "Delivery"}, + {"day": 24, "status": "Delivery"}, + {"day": 25, "status": "Delivery"}, + {"day": 26, "status": "Delivery"}, + ]; + } + + Color _getBackgroundColor(String status) { + switch (status) { + case "Delivered": + return const Color(0xFFE6F4EA); + case "Cancelled": + return const Color(0xFFFDE8E8); + case "Rescheduled": + return const Color(0xFFF2F2F2); + case "Delivery": + return const Color(0xFFEFF4FF); + default: + return Colors.white; + } + } + + Color _getTextColor(String status) { + switch (status) { + case "Delivered": + return Colors.green; + case "Cancelled": + return Colors.red; + case "Rescheduled": + return Colors.black54; + case "Delivery": + return const Color(0xFF3B6FE0); + default: + return Colors.black87; + } + } + + Widget _getStatusIcon(String status) { + switch (status) { + case "Delivered": + return const Icon(Icons.check, size: 16, color: Colors.green); + case "Cancelled": + return const Icon(Icons.close, size: 16, color: Colors.red); + case "Rescheduled": + return const Icon(Icons.access_time, size: 16, color: Colors.black54); + case "Delivery": + return const Icon(Icons.local_shipping, size: 16, color: Color(0xFF3B6FE0)); + default: + return const SizedBox.shrink(); + } + } + + String _getMonthYear() { + return DateFormat('MMM yyyy').format(_focusedMonth).toUpperCase(); + } + + int _daysInMonth(DateTime date) { + final firstDayThisMonth = DateTime(date.year, date.month, 1); + final firstDayNextMonth = DateTime(date.year, date.month + 1, 1); + return firstDayNextMonth.difference(firstDayThisMonth).inDays; + } + + @override + Widget build(BuildContext context) { + final int totalDays = _daysInMonth(_focusedMonth); + final int firstWeekday = DateTime(_focusedMonth.year, _focusedMonth.month, 1).weekday; + final int totalSlots = totalDays + (firstWeekday - 1); + + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black), + onPressed: () => Navigator.pop(context), + ), + title: Text("Calendar", style: fontTextStyle(16, Colors.black, FontWeight.w600)), + actions: const [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Icon(Icons.calendar_month_outlined, color: Color(0xFF8270DB)), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Icon(Icons.notifications_none_rounded, color: Colors.black87), + ), + ], + ), + body: Column( + children: [ + const SizedBox(height: 8), + Text(_getMonthYear(), style: fontTextStyle(16, Colors.black, FontWeight.w600)), + const SizedBox(height: 8), + + // Weekdays Row + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Row( + children: const [ + Expanded(child: Center(child: Text("MON"))), + Expanded(child: Center(child: Text("TUE"))), + Expanded(child: Center(child: Text("WED"))), + Expanded(child: Center(child: Text("THU"))), + Expanded(child: Center(child: Text("FRI"))), + Expanded(child: Center(child: Text("SAT"))), + Expanded(child: Center(child: Text("SUN"))), + ], + ), + ), + const SizedBox(height: 8), + + // Calendar Grid + Expanded( + child: GridView.builder( + padding: const EdgeInsets.symmetric(horizontal: 4), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 7, + crossAxisSpacing: 4, + mainAxisSpacing: 4, + ), + itemCount: totalSlots, + itemBuilder: (context, index) { + if (index < firstWeekday - 1) { + return const SizedBox.shrink(); + } + final day = index - (firstWeekday - 2); + final status = _calendarData + .firstWhere( + (item) => item['day'] == day, + orElse: () => {"status": ""}, + )['status'] + .toString(); + + return Container( + decoration: BoxDecoration( + color: _getBackgroundColor(status), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade300), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "$day", + style: fontTextStyle(13, _getTextColor(status), FontWeight.w600), + ), + const SizedBox(height: 4), + _getStatusIcon(status), + ], + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/common/dashboard.dart b/lib/common/dashboard.dart index f3d8214..4b51946 100644 --- a/lib/common/dashboard.dart +++ b/lib/common/dashboard.dart @@ -19,6 +19,7 @@ import 'package:supplier_new/set_rates/set_rates.dart'; import '../login/login.dart'; import '../resources/resources_fleet.dart'; import '../resources/resources_main.dart'; +import 'calander.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); @@ -318,7 +319,14 @@ class _DashboardScreenState extends State { padding: const EdgeInsets.fromLTRB(10, 10, 0, 10), child: IconButton( icon: Image.asset('images/calendar_appbar.png'), - onPressed: () {}, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DeliveryCalendarScreen(), + ), + ); + }, ), ), Padding( diff --git a/lib/common/settings.dart b/lib/common/settings.dart index 46b517e..a1d764f 100644 --- a/lib/common/settings.dart +++ b/lib/common/settings.dart @@ -23,6 +23,19 @@ TextStyle fontTextStyle(double fontSize,Color fontColor,FontWeight weight) { ); } +TextStyle fontTextStylewithUnderline(double fontSize,Color fontColor,FontWeight weight, Color underlineColor) { + return GoogleFonts.inter( + textStyle: TextStyle( + fontSize: fontSize, + fontWeight: weight, + color:fontColor, + decoration: TextDecoration.underline, + decorationColor: underlineColor, + decorationThickness: 1, + ), + ); +} + TextStyle fontTextStyleHeight(double fontSize,Color fontColor,FontWeight weight,double height) { return GoogleFonts.inter( textStyle: TextStyle( @@ -147,7 +160,9 @@ class AppSettings{ static String addDriversUrl = host + 'addDeliveryboys'; static String addSourceLocationsUrl = host + 'addSource'; static String getSourceLoctaionsUrl = host + 'getallsourcesofsupplier'; - + static String setRatesDailyUrl = host + 'tankers'; + static String getSupplierDetailsUrl = host + 'suppliers'; + static String updatePumpFeeUrl = host + 'suppliers'; static String formDouble(dynamic s) { @@ -525,8 +540,9 @@ class AppSettings{ } static Future addDrivers(payload) async { - var response = await http.post(Uri.parse(addDriversUrl + '/' + supplierId), - body: json.encode(payload), headers: await buildRequestHeaders()); + + var uri =Uri.parse(addDriversUrl + '/' + supplierId); + var response = await http.post(uri, body: json.encode(payload), headers: await buildRequestHeaders()); if (response.statusCode == 200) { try { @@ -540,8 +556,7 @@ class AppSettings{ } else if (response.statusCode == 401) { bool status = await AppSettings.resetToken(); if (status) { - response = await http.post(Uri.parse(addTankerUrl + '/' + supplierId), - body: json.encode(payload), headers: await buildRequestHeaders()); + response = await http.post(uri, body: json.encode(payload), headers: await buildRequestHeaders()); if (response.statusCode == 200) { return true; } else { @@ -580,7 +595,9 @@ class AppSettings{ } static Future addSourceLocations(payload) async { - var response = await http.post(Uri.parse(addSourceLocationsUrl + '/' + supplierId), + + var uri = Uri.parse(addSourceLocationsUrl + '/' + supplierId); + var response = await http.post(uri, body: json.encode(payload), headers: await buildRequestHeaders()); if (response.statusCode == 200) { @@ -595,7 +612,7 @@ class AppSettings{ } else if (response.statusCode == 401) { bool status = await AppSettings.resetToken(); if (status) { - response = await http.post(Uri.parse(addTankerUrl + '/' + supplierId), + response = await http.post(uri, body: json.encode(payload), headers: await buildRequestHeaders()); if (response.statusCode == 200) { return true; @@ -610,6 +627,92 @@ class AppSettings{ } } + static Future setRatesDaily(payload) async { + var uri = Uri.parse(setRatesDailyUrl + '/' + supplierId+'/update-prices'); + + 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 getSupplierDetails() async { + var uri = Uri.parse(getSupplierDetailsUrl+'/'+supplierId); + //uri = uri.replace(query: 'supplierId=$supplierId'); + + var response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else if (response.statusCode == 401) { + bool status = await AppSettings.resetToken(); + if (status) { + response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else { + return ''; + } + } else { + return ''; + } + } else { + return ''; + } + } + + static Future updatePumpFee(payload) async { + + var uri =Uri.parse(updatePumpFeeUrl + '/' + supplierId+'/pumping-fee'); + 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; + } + } + /*Apis ends here*/ diff --git a/lib/orders/all_orders.dart b/lib/orders/all_orders.dart index 8b180e9..b894cf7 100644 --- a/lib/orders/all_orders.dart +++ b/lib/orders/all_orders.dart @@ -7,6 +7,9 @@ import 'package:supplier_new/orders/orders_model.dart'; import 'package:supplier_new/orders/search_order_appbar.dart'; import 'package:intl/intl.dart'; +import 'assign_driver.dart'; +import 'cancel_order.dart'; + class AllOrders extends StatefulWidget { final String navigationFrom; AllOrders({ @@ -86,7 +89,14 @@ class _AllOrdersState extends State { print("Help tapped"); }, ):null, - body: SingleChildScrollView( + body: isLoading + ? const Center(child: CircularProgressIndicator()) + : ordersList.isEmpty?Center( + child: Text( + 'No Data Available', + style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700), + ), + ):SingleChildScrollView( child: Padding( padding: EdgeInsets.all(16), @@ -205,7 +215,10 @@ class _AllOrdersState extends State { ), // Orders list for this date - ...ordersForDate.map((order) => OrderCard(order: order)), + ...ordersForDate.map((order) => OrderCard( + order: order, + onRefresh: _fetchOrders, // πŸ‘ˆ pass parent function here + )), const SizedBox(height: 12), ], ); @@ -276,8 +289,9 @@ class FilterChipWidget extends StatelessWidget { class OrderCard extends StatelessWidget { final OrdersModel order; + final VoidCallback? onRefresh; - const OrderCard({super.key, required this.order}); + const OrderCard({super.key, required this.order, this.onRefresh}); Color _getStatusColor() { switch (order.status.toLowerCase()) { @@ -403,27 +417,74 @@ class OrderCard extends StatelessWidget { style:fontTextStyle(8,Color(0XFF646566),FontWeight.w400) ), const SizedBox(height: 12), - Visibility( - visible: order.status.toLowerCase().toString()=='advance_paid', - child: GestureDetector( - onTap: (){ - - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(22), - border: Border.all(color: const Color(0XFF939495)), - ), - child: Padding( - padding: EdgeInsets.fromLTRB(8,4,8,4), - child: Text( - "Assign", - style: fontTextStyle( - 14, const Color(0XFF515253), FontWeight.w400), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + 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), + ), + ); + + // 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)), + ), + child: Padding( + padding: EdgeInsets.fromLTRB(8,4,8,4), + child: Text( + "Assign", + style: fontTextStyle( + 14, const Color(0XFF515253), FontWeight.w400), + ), + ) ), - ) - ), - ),), + ),), + Visibility( + visible: order.status.toLowerCase().toString()=='advance_paid', + child: GestureDetector( + onTap: ()async{ + final result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CancelOrderScreen(order: order), + ), + ); + + // 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(0XFFE2483D)), + color: Color(0XFFE2483D) + ), + child: Padding( + padding: EdgeInsets.fromLTRB(8,4,8,4), + child: Text( + "Cancel", + style: fontTextStyle( + 14, const Color(0XFFFFFFFF), FontWeight.w400), + ), + ) + ), + ),), + ], + ) /*Text( diff --git a/lib/orders/assign_driver.dart b/lib/orders/assign_driver.dart new file mode 100644 index 0000000..8841c69 --- /dev/null +++ b/lib/orders/assign_driver.dart @@ -0,0 +1,693 @@ +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'; + +class AssignDriverScreen extends StatefulWidget { + var order; + var status; + AssignDriverScreen({this.order, this.status}); + @override + State createState() => _AssignDriverScreenState(); +} + +class _AssignDriverScreenState extends State { + int advancePayable = 0; + int advance = 0; + double amountToPayAfterDelivery = 0.0; + double totalFare = 0.0; + bool isLoading = false; + List driversList = []; + + @override + void initState() { + // TODO: implement initState + super.initState(); + _fetchDrivers(); + advance = 150; + advancePayable = advance; + totalFare = advance + double.parse(widget.order.quoted_amount); + amountToPayAfterDelivery = totalFare - advancePayable; + } + + Future _fetchDrivers() async { + setState(() => isLoading = true); + try { + final response = await AppSettings.getDrivers(); + final data = (jsonDecode(response)['data'] as List) + .map((e) => DriversModel.fromJson(e)) + .toList(); + if (!mounted) return; + setState(() { + driversList = data; + isLoading = false; + }); + } catch (e) { + debugPrint("⚠️ Error fetching drivers: $e"); + setState(() => isLoading = false); + } + } + + void _showAssignDriverBottomSheet() { + int? selectedDriverIndex; // βœ… Track selected driver + + showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + enableDrag: false, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (context) { + return WillPopScope( // βœ… block Android back button + 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, + children: [ + Text( + "Assign Driver", + style: fontTextStyle(16, const Color(0XFF2A2A2A), FontWeight.w600), + ), + GestureDetector( + onTap: (){ + Navigator.pop(context); + }, + child: Image.asset('images/cross.png', height: 24, width: 24,color: Color(0XFF2A2A2A),), + ) + ], + ), + const SizedBox(height: 16), + + // 🏒 Order Info + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + border: Border.all(color: const Color(0XFFC9C2F0), width: 0.5), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.order.building_name, + style: fontTextStyle(20, const Color(0XFF2D2E30), FontWeight.w600), + ), + Text( + widget.order.displayAddress, + style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + const Spacer(), + Text( + '${widget.order.distanceInKm} Km', + style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + ), + const SizedBox(height: 16), + + Text( + "SELECT DRIVER", + style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600), + ), + const SizedBox(height: 12), + + // 🧍 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 + setModalState(() { + selectedDriverIndex = idx; + }); + } else { + AppSettings.longFailedToast( + 'Only available drivers can be selected', + ); + } + }, + child: Opacity( + opacity: isAvailable ? 1 : 1, // πŸ‘ˆ grey out non-available + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide( + color: isSelected + ? const Color(0XFF8270DB) + : const Color(0XFFC3C4C4), + width: 1, + ), + ), + color: isSelected + ? const Color(0XFFEDEBFF) + : Colors.white, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + children: [ + Image.asset( + 'images/avatar.png', + fit: BoxFit.cover, + width: 20, + height: 20, + ), + SizedBox( + width: MediaQuery.of(context).size.width * .016), + Expanded( + child: Text( + d.driver_name, + style: fontTextStyle( + 14, + const Color(0XFF2D2E30), + FontWeight.w500, + ), + ), + ), + 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: statusColor), + ), + child: Text( + d.status, + style: fontTextStyle( + 10, + statusColor, + FontWeight.w400, + ), + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + ), + + const SizedBox(height: 16), + + // 🟣 Assign Button + SizedBox( + width: double.infinity, + child: GestureDetector( + onTap: () async { + if (selectedDriverIndex == null) { + AppSettings.longFailedToast('Please select driver'); + return; + } + + final selectedDriver = driversList[selectedDriverIndex!]; + + // βœ… Call your API here + // await _assignDriverApi(selectedDriver.driver_id); + + if (context.mounted) Navigator.pop(context); + }, + child: Container( + decoration: BoxDecoration( + color: const Color(0XFF8270DB), + borderRadius: BorderRadius.circular(24), + ), + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 12), + child: Text( + 'Assign', + style: fontTextStyle( + 14, const Color(0XFFFFFFFF), FontWeight.w500), + ), + ), + ), + ), + ], + ), + ); + }, + ); + }, + ),); + }, + ); + } + + + + + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + extendBodyBehindAppBar: true, + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + scrolledUnderElevation: 0, + title: Text( + '', + style: fontTextStyle(16, Color(0XFF2A2A2A), FontWeight.w600), + ), + iconTheme: IconThemeData(color: Color(0XFF2A2A2A)), + actions: [ + Row( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(0, 10, 10, 10), + child: IconButton( + icon: Image.asset( + 'images/help_appbar.png', + height: 20, + width: 20, + color: Color(0XFFFFFFFF), + ), + onPressed: () {}, + ), + ) + ], + ) + ], + leading: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Padding( + padding: + const EdgeInsets.fromLTRB(8, 8, 8, 8), // Add padding if needed + child: Image.asset( + 'images/backbutton_appbar.png', // Replace with your image path + fit: BoxFit.contain, + color: Color(0XFFFFFFFF), + height: 24, + width: 24, + ), + ), + ), + ), + + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// πŸ”Ή Top Card with Image + Stack( + clipBehavior: Clip.none, + children: [ + /// πŸ”Ή Background Image + ClipRRect( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(0), + bottomRight: Radius.circular(0), + ), + child: Image.asset( + "images/building.png", + height: 220, + width: double.infinity, + fit: BoxFit.cover, + ), + ), + + /// πŸ”Ή Floating Info Card (half on image, half below) + Positioned( + bottom: -40, // pulls the card out by 40px + left: 12, + right: 12, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 6, + offset: Offset(0, 3), + ), + ], + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /*Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Color(0XFFFFFFFF), + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: widget.status.statusColor, + width: 0.5, + ), + ), + child: Text(widget.status.status, + style: fontTextStyle( + 12, + widget.status.statusColor, + FontWeight.w500)), + ),*/ + Text( + widget.order.building_name, + style: fontTextStyle( + 20, const Color(0XFF2D2E30), FontWeight.w600), + ), + SizedBox(height: 4), + Text( + widget.order.displayAddress, + style: fontTextStyle( + 12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + const Spacer(), + Text( + widget.order.distanceInKm.toString() + 'Km', + style: fontTextStyle( + 12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + ), + ), + ], + ), + SizedBox( + height: MediaQuery.of(context).size.height * .08, + ), + + /// πŸ”Ή Order Details + Padding( + padding: EdgeInsets.fromLTRB(16, 0, 16, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "ORDER DETAILS", + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .011, + ), + _detailTwoRow( + "Tanker Price", + "β‚Ή${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}", + "images/financialsBottomIcon.png", + "", + "", + ""), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Water Type", + "${widget.order.type_of_water}", + "images/water.png", + "Date of Delivery", + "${widget.order.date}", + "images/calendar_appbar.png", + ), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Capacity", + "${widget.order.capacity}", + "images/capacity.png", + "Time of Delivery", + "${widget.order.time}", + "images/time.png", + ), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Quantity", + "${widget.order.quantity}", + "images/quantity.png", + "Booking Charges", + advance.toString(), + "images/advance.png", + ), + ], + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .008, + ), + + /// πŸ”Ή Additional Details + Padding( + padding: EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "ADDITIONAL DETAILS", + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .011, + ), + Text( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " + "aliquip ex ea commodo consequat.", + style: fontTextStyle( + 12, const Color(0XFF646566), FontWeight.w400), + ), + ], + )), + ], + ), + ), + + /// πŸ”Ή Bottom Action Buttons + bottomNavigationBar: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Color(0XFFFFFFFF), + border: Border(top: BorderSide(color: Color(0XFFF5F6F6))), + ), + child: Row( + children: [ + Expanded( + child: OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: Color(0XFFFFFFFF), + backgroundColor: Colors.white, + side: BorderSide(color: Color(0XFFFFFFFF)), + padding: EdgeInsets.symmetric(vertical: 10), + ), + onPressed: () async { + AppSettings.preLoaderDialog(context); + + bool isOnline = await AppSettings.internetConnectivity(); + + if (isOnline) { + var payload = new Map(); + payload["supplierId"] = AppSettings.supplierId; + payload["amount"] = int.parse(widget.order.quoted_amount); + payload["delivery_charges"] = advance; + payload["action"] = 'reject'; + + bool status = await AppSettings.acceptOrderRequests( + payload, widget.order.dbId); + + try { + if (status) { + Navigator.of(context, rootNavigator: true).pop(); + AppSettings.longSuccessToast( + "Order request rejected Successfully"); + Navigator.pop(context, true); + } else { + Navigator.of(context, rootNavigator: true).pop(); + AppSettings.longFailedToast( + "reject of order request Failed"); + } + } catch (e) { + Navigator.of(context, rootNavigator: true).pop(); + print(e); + } + } else { + Navigator.of(context, rootNavigator: true).pop(); + AppSettings.longFailedToast("Please Check internet"); + } + }, + child: Text( + "CANCEL", + style: fontTextStyle( + 14, const Color(0XFFE2483D), FontWeight.w400), + ), + ), + ), + SizedBox(width: 8), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Color(0XFF8270DB), + foregroundColor: Color(0XFFFFFFFF), + padding: EdgeInsets.symmetric(vertical: 10), + ), + onPressed: () async { + _showAssignDriverBottomSheet(); + }, + child: Text( + "Assign Driver", + style: fontTextStyle( + 14, const Color(0XFFFFFFFF), FontWeight.w400), + ), + ), + ), + ], + )), + ); + } + + /// πŸ”Ή Helper widget for rows + Widget _detailRow(String title, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: fontTextStyle(12, const Color(0XFF646566), FontWeight.w400), + ), + Text( + value, + style: fontTextStyle(12, const Color(0XFF2D2E30), FontWeight.w500), + ), + ], + ), + ); + } + + Widget _detailTwoRow( + String title1, + String value1, + String path1, + String title2, + String value2, + String path2, { + EdgeInsetsGeometry padding = const EdgeInsets.symmetric(vertical: 6), + }) { + final titleStyle = fontTextStyle(12, Color(0XFF646566), FontWeight.w400); + final valueStyle = fontTextStyle(12, Color(0XFF343637), FontWeight.w500); + + Widget _col(String t, String v, String path) { + return Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (path.isNotEmpty) + Image.asset( + path, + fit: BoxFit.contain, + height: 20, + width: 20, + color: const Color(0XFFC3C4C4), + ), + if (path.isNotEmpty) const SizedBox(width: 6), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t, + style: titleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis), + Text(v, + style: valueStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis), + ], + ), + ) + ], + ), + ), + ); + } + + return Padding( + padding: padding, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _col(title1, value1, path1), + _col(title2, value2, path2), + ], + ), + ); + } +} diff --git a/lib/orders/cancel_order.dart b/lib/orders/cancel_order.dart new file mode 100644 index 0000000..8db0dc6 --- /dev/null +++ b/lib/orders/cancel_order.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; + +import '../common/settings.dart'; + +class CancelOrderScreen extends StatefulWidget { + var order; + var status; + CancelOrderScreen({this.order, this.status}); + + @override + State createState() => _CancelOrderScreenState(); +} + +class _CancelOrderScreenState extends State { + String? selectedReason; + final TextEditingController reasonController = TextEditingController(); + + final List reasons = [ + "Changed my mind", + "Tanker not available", + "Vehicle got damaged", + "Buyer asked me to cancel", + "Other", + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + scrolledUnderElevation: 0, + title: Text( + 'Cancel Order', + style: fontTextStyle(14, Color(0XFF2A2A2A), FontWeight.w500), + ), + iconTheme: IconThemeData(color: Color(0XFF2A2A2A)), + leading: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Padding( + padding: + const EdgeInsets.fromLTRB(8, 8, 8, 8), // Add padding if needed + child: Image.asset( + 'images/backbutton_appbar.png', // Replace with your image path + fit: BoxFit.contain, + color: Color(0XFF2A2A2A), + height: 24, + width: 24, + ), + ), + ), + ), + body: Padding( + padding:EdgeInsets.all(24.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Why are you cancelling?", + style: fontTextStyle(14, Color(0XFF2A2A2A), FontWeight.w600), + ), + SizedBox(height:MediaQuery.of(context).size.height * .008,), + + // Radio buttons + ...reasons.map((reason) { + return RadioListTile( + value: reason, + groupValue: selectedReason, + activeColor: primaryColor, + contentPadding: EdgeInsets.zero, + title: Text( + reason, + style: + fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w400), + ), + onChanged: (value) { + setState(() { + selectedReason = value; + }); + }, + ); + }).toList(), + + if (selectedReason == "Other") + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: TextFormField( + controller: reasonController, + maxLines: 5, + style: fontTextStyle(14, Color(0XFF101214), FontWeight.w400), + cursorColor: Color(0XFF1D7AFC), + textCapitalization: TextCapitalization.sentences, + decoration: textFormFieldDecorationHintText( + Icons.phone, + 'Describe your reason..', + ), + ), + ), + + SizedBox(height:MediaQuery.of(context).size.height * .008,), + Text( + "Cancellation Policy", + style:fontTextStyle(14, Color(0XFF2A2A2A), FontWeight.w600), + ), + SizedBox(height:MediaQuery.of(context).size.height * .016,), + Text( + 'Cancel anytime before delivery starts. If it’s already on the way, a β‚Ή100 cancellation fee may apply. Please review our full terms and conditions for more details.', + style:fontTextStyle(12, Color(0XFF2A2A2A), FontWeight.w400), + ), + SizedBox(height:MediaQuery.of(context).size.height * .016,), + GestureDetector( + onTap: () { + // open terms page + }, + child: Text( + "View Terms & Conditions", + style: fontTextStylewithUnderline(10, Color(0XFF4692FD), FontWeight.w400,Color(0XFF4692FD)), + ), + ), + + ], + ), + ), + ), + bottomNavigationBar: SafeArea( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Row( + children: [ + // Secondary + Expanded( + child: OutlinedButton( + onPressed: () => Navigator.pop(context), + style: OutlinedButton.styleFrom( + side: const BorderSide(color: Color(0xFF2A2A2A)), + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + ), + child: Text( + "Don’t cancel", + style: fontTextStyle(14, const Color(0xFF2A2A2A), FontWeight.w600), + ), + ), + ), + const SizedBox(width: 12), + // Primary + Expanded( + child: ElevatedButton( + onPressed: () { + // TODO: call cancel API + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0XFFE2483D), + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + ), + child: Text( + "Cancel Order", + style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600), + ), + ), + ), + ], + ), + ), + ), + + ); + } +} + diff --git a/lib/orders/change_driver.dart b/lib/orders/change_driver.dart new file mode 100644 index 0000000..53e8429 --- /dev/null +++ b/lib/orders/change_driver.dart @@ -0,0 +1,730 @@ +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'; + +class ChangeDriverScreen extends StatefulWidget { + var order; + var status; + ChangeDriverScreen({this.order, this.status}); + @override + State createState() => _ChangeDriverScreenState(); +} + +class _ChangeDriverScreenState extends State { + int advancePayable = 0; + int advance = 0; + double amountToPayAfterDelivery = 0.0; + double totalFare = 0.0; + bool isLoading = false; + List driversList = []; + + @override + void initState() { + // TODO: implement initState + super.initState(); + _fetchDrivers(); + advance = 150; + advancePayable = advance; + totalFare = advance + double.parse(widget.order.quoted_amount); + amountToPayAfterDelivery = totalFare - advancePayable; + } + + Future _fetchDrivers() async { + setState(() => isLoading = true); + try { + final response = await AppSettings.getDrivers(); + final data = (jsonDecode(response)['data'] as List) + .map((e) => DriversModel.fromJson(e)) + .toList(); + if (!mounted) return; + setState(() { + driversList = data; + isLoading = false; + }); + } catch (e) { + debugPrint("⚠️ Error fetching drivers: $e"); + setState(() => isLoading = false); + } + } + + void _showAssignDriverBottomSheet() { + int? selectedDriverIndex; // βœ… Track selected driver + + showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + enableDrag: false, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (context) { + return WillPopScope( // βœ… block Android back button + 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, + children: [ + Text( + "Assign Driver", + style: fontTextStyle(16, const Color(0XFF2A2A2A), FontWeight.w600), + ), + GestureDetector( + onTap: (){ + Navigator.pop(context); + }, + child: Image.asset('images/cross.png', height: 24, width: 24,color: Color(0XFF2A2A2A),), + ) + ], + ), + const SizedBox(height: 16), + + // 🏒 Order Info + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + border: Border.all(color: const Color(0XFFC9C2F0), width: 0.5), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.order.building_name, + style: fontTextStyle(20, const Color(0XFF2D2E30), FontWeight.w600), + ), + Text( + widget.order.displayAddress, + style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + const Spacer(), + Text( + '${widget.order.distanceInKm} Km', + style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + ), + const SizedBox(height: 16), + + Text( + "SELECT DRIVER", + style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600), + ), + const SizedBox(height: 12), + + // 🧍 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 + setModalState(() { + selectedDriverIndex = idx; + }); + } else { + AppSettings.longFailedToast( + 'Only available drivers can be selected', + ); + } + }, + child: Opacity( + opacity: isAvailable ? 1 : 1, // πŸ‘ˆ grey out non-available + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide( + color: isSelected + ? const Color(0XFF8270DB) + : const Color(0XFFC3C4C4), + width: 1, + ), + ), + color: isSelected + ? const Color(0XFFEDEBFF) + : Colors.white, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + children: [ + Image.asset( + 'images/avatar.png', + fit: BoxFit.cover, + width: 20, + height: 20, + ), + SizedBox( + width: MediaQuery.of(context).size.width * .016), + Expanded( + child: Text( + d.driver_name, + style: fontTextStyle( + 14, + const Color(0XFF2D2E30), + FontWeight.w500, + ), + ), + ), + 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: statusColor), + ), + child: Text( + d.status, + style: fontTextStyle( + 10, + statusColor, + FontWeight.w400, + ), + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + ), + + const SizedBox(height: 16), + + // 🟣 Assign Button + SizedBox( + width: double.infinity, + child: GestureDetector( + onTap: () async { + if (selectedDriverIndex == null) { + AppSettings.longFailedToast('Please select driver'); + return; + } + + final selectedDriver = driversList[selectedDriverIndex!]; + + // βœ… Call your API here + // await _assignDriverApi(selectedDriver.driver_id); + + if (context.mounted) Navigator.pop(context); + }, + child: Container( + decoration: BoxDecoration( + color: const Color(0XFF8270DB), + borderRadius: BorderRadius.circular(24), + ), + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 12), + child: Text( + 'Assign', + style: fontTextStyle( + 14, const Color(0XFFFFFFFF), FontWeight.w500), + ), + ), + ), + ), + ], + ), + ); + }, + ); + }, + ),); + }, + ); + } + + + + + + @override + Widget build(BuildContext context) { + + return Scaffold( + backgroundColor: Colors.white, + extendBodyBehindAppBar: true, + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + scrolledUnderElevation: 0, + title: Text( + '', + style: fontTextStyle(16, Color(0XFF2A2A2A), FontWeight.w600), + ), + iconTheme: IconThemeData(color: Color(0XFF2A2A2A)), + actions: [ + Row( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(0, 10, 10, 10), + child: IconButton( + icon: Image.asset( + 'images/help_appbar.png', + height: 20, + width: 20, + color: Color(0XFFFFFFFF), + ), + onPressed: () {}, + ), + ) + ], + ) + ], + leading: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Padding( + padding: + const EdgeInsets.fromLTRB(8, 8, 8, 8), // Add padding if needed + child: Image.asset( + 'images/backbutton_appbar.png', // Replace with your image path + fit: BoxFit.contain, + color: Color(0XFFFFFFFF), + height: 24, + width: 24, + ), + ), + ), + ), + + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// πŸ”Ή Top Card with Image + Stack( + clipBehavior: Clip.none, + children: [ + /// πŸ”Ή Background Image + ClipRRect( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(0), + bottomRight: Radius.circular(0), + ), + child: Image.asset( + "images/building.png", + height: 220, + width: double.infinity, + fit: BoxFit.cover, + ), + ), + + /// πŸ”Ή Floating Info Card (half on image, half below) + Positioned( + bottom: -40, // pulls the card out by 40px + left: 12, + right: 12, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 6, + offset: Offset(0, 3), + ), + ], + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /*Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Color(0XFFFFFFFF), + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: widget.status.statusColor, + width: 0.5, + ), + ), + child: Text(widget.status.status, + style: fontTextStyle( + 12, + widget.status.statusColor, + FontWeight.w500)), + ),*/ + Text( + widget.order.building_name, + style: fontTextStyle( + 20, const Color(0XFF2D2E30), FontWeight.w600), + ), + SizedBox(height: 4), + Text( + widget.order.displayAddress, + style: fontTextStyle( + 12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + const Spacer(), + Text( + widget.order.distanceInKm.toString() + 'Km', + style: fontTextStyle( + 12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + ), + ), + ], + ), + SizedBox( + height: MediaQuery.of(context).size.height * .08, + ), + + /// πŸ”Ή Order Details + Padding( + padding: EdgeInsets.fromLTRB(16, 0, 16, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "ORDER DETAILS", + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .011, + ), + _detailTwoRow( + "Tanker Price", + "β‚Ή${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}", + "images/financialsBottomIcon.png", + "", + "", + ""), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Water Type", + "${widget.order.type_of_water}", + "images/water.png", + "Date of Delivery", + "${widget.order.date}", + "images/calendar_appbar.png", + ), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Capacity", + "${widget.order.capacity}", + "images/capacity.png", + "Time of Delivery", + "${widget.order.time}", + "images/time.png", + ), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Quantity", + "${widget.order.quantity}", + "images/quantity.png", + "Booking Charges", + advance.toString(), + "images/advance.png", + ), + ], + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .008, + ), + + /// πŸ”Ή Additional Details + Padding( + padding: EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "ADDITIONAL DETAILS", + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .011, + ), + Text( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " + "aliquip ex ea commodo consequat.", + style: fontTextStyle( + 12, const Color(0XFF646566), FontWeight.w400), + ), + Image.asset( + 'images/avatar.png', + fit: BoxFit.cover, + width: 20, + height: 20, + ), + SizedBox( + width: MediaQuery.of(context).size.width * .016), + Expanded( + child: Text( + 'd.driver_name', + style: fontTextStyle( + 14, + const Color(0XFF2D2E30), + FontWeight.w500, + ), + ), + ), + 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, + ), + ), + ), + ], + )), + ], + ), + ), + + /// πŸ”Ή Bottom Action Buttons + bottomNavigationBar: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Color(0XFFFFFFFF), + border: Border(top: BorderSide(color: Color(0XFFF5F6F6))), + ), + child: Row( + children: [ + Expanded( + child: OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: Color(0XFFFFFFFF), + backgroundColor: Colors.white, + side: BorderSide(color: Color(0XFFFFFFFF)), + padding: EdgeInsets.symmetric(vertical: 10), + ), + onPressed: () async { + AppSettings.preLoaderDialog(context); + + bool isOnline = await AppSettings.internetConnectivity(); + + if (isOnline) { + var payload = new Map(); + payload["supplierId"] = AppSettings.supplierId; + payload["amount"] = int.parse(widget.order.quoted_amount); + payload["delivery_charges"] = advance; + payload["action"] = 'reject'; + + bool status = await AppSettings.acceptOrderRequests( + payload, widget.order.dbId); + + try { + if (status) { + Navigator.of(context, rootNavigator: true).pop(); + AppSettings.longSuccessToast( + "Order request rejected Successfully"); + Navigator.pop(context, true); + } else { + Navigator.of(context, rootNavigator: true).pop(); + AppSettings.longFailedToast( + "reject of order request Failed"); + } + } catch (e) { + Navigator.of(context, rootNavigator: true).pop(); + print(e); + } + } else { + Navigator.of(context, rootNavigator: true).pop(); + AppSettings.longFailedToast("Please Check internet"); + } + }, + child: Text( + "CANCEL", + style: fontTextStyle( + 14, const Color(0XFFE2483D), FontWeight.w400), + ), + ), + ), + SizedBox(width: 8), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Color(0XFF8270DB), + foregroundColor: Color(0XFFFFFFFF), + padding: EdgeInsets.symmetric(vertical: 10), + ), + onPressed: () async { + _showAssignDriverBottomSheet(); + }, + child: Text( + "Change Order", + style: fontTextStyle( + 14, const Color(0XFFFFFFFF), FontWeight.w400), + ), + ), + ), + ], + )), + ); + } + + /// πŸ”Ή Helper widget for rows + Widget _detailRow(String title, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: fontTextStyle(12, const Color(0XFF646566), FontWeight.w400), + ), + Text( + value, + style: fontTextStyle(12, const Color(0XFF2D2E30), FontWeight.w500), + ), + ], + ), + ); + } + + Widget _detailTwoRow( + String title1, + String value1, + String path1, + String title2, + String value2, + String path2, { + EdgeInsetsGeometry padding = const EdgeInsets.symmetric(vertical: 6), + }) { + final titleStyle = fontTextStyle(12, Color(0XFF646566), FontWeight.w400); + final valueStyle = fontTextStyle(12, Color(0XFF343637), FontWeight.w500); + + Widget _col(String t, String v, String path) { + return Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (path.isNotEmpty) + Image.asset( + path, + fit: BoxFit.contain, + height: 20, + width: 20, + color: const Color(0XFFC3C4C4), + ), + if (path.isNotEmpty) const SizedBox(width: 6), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t, + style: titleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis), + Text(v, + style: valueStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis), + ], + ), + ) + ], + ), + ), + ); + } + + return Padding( + padding: padding, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _col(title1, value1, path1), + _col(title2, value2, path2), + ], + ), + ); + } +} diff --git a/lib/orders/order_requests.dart b/lib/orders/order_requests.dart index a7766e5..78fe02b 100644 --- a/lib/orders/order_requests.dart +++ b/lib/orders/order_requests.dart @@ -15,12 +15,40 @@ class OrderRequestsPage extends StatefulWidget { class _OrderRequestsPageState extends State { final TextEditingController searchController = TextEditingController(); List orderRequestsList = []; + List filteredList = []; bool isLoading = false; @override void initState() { super.initState(); _fetchOrders(); + // πŸ”Ή Add listener for search + searchController.addListener(() { + _filterOrders(searchController.text); + }); + + } + + void _filterOrders(String query) { + final lowerQuery = query.toLowerCase(); + + setState(() { + if (lowerQuery.isEmpty) { + filteredList = orderRequestsList; + } else { + filteredList = orderRequestsList.where((order) { + final title = order.building_name?.toLowerCase() ?? ''; + final address = order.displayAddress?.toLowerCase() ?? ''; + final waterType = order.type_of_water?.toLowerCase() ?? ''; + final capacity = order.capacity?.toLowerCase() ?? ''; + + return title.contains(lowerQuery) || + address.contains(lowerQuery) || + waterType.contains(lowerQuery) || + capacity.contains(lowerQuery); + }).toList(); + } + }); } Future _fetchOrders() async { @@ -34,6 +62,7 @@ class _OrderRequestsPageState extends State { if (!mounted) return; setState(() { orderRequestsList = data; + filteredList = data; isLoading = false; }); } catch (e) { @@ -72,8 +101,8 @@ class _OrderRequestsPageState extends State { if (difference.inMinutes < 2) { status = "New"; color = const Color(0XFF1D7AFC); // Blue - } else if (difference.inMinutes < 30) { - final remaining = 30 - difference.inMinutes; + } else if (difference.inMinutes < 300) { + final remaining = 300 - difference.inMinutes; status = "Expires in ${remaining}m"; // show time for pending color = difference.inMinutes < 10 ? const Color(0XFFE56910) @@ -111,10 +140,15 @@ class _OrderRequestsPageState extends State { Expanded( child: isLoading ? const Center(child: CircularProgressIndicator()) - : ListView.builder( - itemCount: orderRequestsList.length, + : filteredList .isEmpty?Center( + child: Text( + 'No Data Available', + style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700), + ), + ):ListView.builder( + itemCount: filteredList .length, itemBuilder: (context, index) { - final order = orderRequestsList[index]; + final order = filteredList [index]; final statusMap = getOrderStatus(order.time ?? "", order.status ?? ""); final status = statusMap['status']; final color = statusMap['color']; @@ -213,6 +247,7 @@ class _OrderRequestsPageState extends State { }, ) ), + const SizedBox(width: 5), Container( height: 42, width: 42, diff --git a/lib/orders/orders_model.dart b/lib/orders/orders_model.dart index 66ccd18..61bf0cf 100644 --- a/lib/orders/orders_model.dart +++ b/lib/orders/orders_model.dart @@ -30,7 +30,7 @@ class OrdersModel { rtvm.building_name = json['buildingName'] ?? ''; rtvm.dbId = json['_id']?? ''; rtvm.address = json['address'] ?? ''; - rtvm.type_of_water = json['typeofwater '] ?? ''; + rtvm.type_of_water = json['typeofwater'] ?? ''; rtvm.capacity = json['capacity'] ?? ''; rtvm.quantity = json['quantity']?? ''; rtvm.time = json['time'] ?? ''; diff --git a/lib/plans/all_plans.dart b/lib/plans/all_plans.dart index 185ccb0..6c29216 100644 --- a/lib/plans/all_plans.dart +++ b/lib/plans/all_plans.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:supplier_new/common/settings.dart'; +import 'package:supplier_new/plans/plan_details.dart'; import 'package:supplier_new/plans/plan_requests.dart'; import 'package:supplier_new/plans/plans_model.dart'; import 'package:supplier_new/plans/search_plan_appbar.dart'; @@ -286,122 +287,132 @@ class PlansCard extends StatelessWidget { Widget build(BuildContext context) { bool isActive = delivery.status == "Active"; - return Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFE5E5E5)), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.03), - blurRadius: 5, - offset: const Offset(0, 2), - ) - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Status Chip - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: isActive ? const Color(0xFFE9F9EE) : const Color(0xFFF2F2F2), - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: isActive ? const Color(0xFF3BB273) : Colors.grey.shade400, - ), - ), - child: Text( - delivery.status, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: isActive ? const Color(0xFF3BB273) : Colors.grey.shade600, - ), - ), - ), - const SizedBox(height: 8), - - // Apartment + Price Row - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - delivery.apartment, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Color(0xFF2A2A2A), - ), - ), - Text( - delivery.price, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Color(0xFF2A2A2A), - ), - ), - ], + return GestureDetector( + onTap: (){ + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PlanDetails(), ), - const SizedBox(height: 4), - - // Liters & Advance - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - delivery.liters, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Color(0xFF7B5AF4), + ); + }, + child: Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: const Color(0xFFE5E5E5)), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.03), + blurRadius: 5, + offset: const Offset(0, 2), + ) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status Chip + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: isActive ? const Color(0xFFE9F9EE) : const Color(0xFFF2F2F2), + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: isActive ? const Color(0xFF3BB273) : Colors.grey.shade400, ), ), - Text( - delivery.advance, - style: const TextStyle( + child: Text( + delivery.status, + style: TextStyle( fontSize: 12, - color: Color(0xFF646566), + fontWeight: FontWeight.w500, + color: isActive ? const Color(0xFF3BB273) : Colors.grey.shade600, ), ), - ], - ), - const SizedBox(height: 12), - - // Deliveries row with background - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - decoration: BoxDecoration( - color: const Color(0xFFF8F6FF), - borderRadius: BorderRadius.circular(8), ), - child: Row( + const SizedBox(height: 8), + + // Apartment + Price Row + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - delivery.deliveries, + delivery.apartment, style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, + fontSize: 15, + fontWeight: FontWeight.w600, color: Color(0xFF2A2A2A), ), ), Text( - delivery.frequency, + delivery.price, style: const TextStyle( - fontSize: 13, + fontSize: 15, + fontWeight: FontWeight.w600, + color: Color(0xFF2A2A2A), + ), + ), + ], + ), + const SizedBox(height: 4), + + // Liters & Advance + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + delivery.liters, + style: const TextStyle( + fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF7B5AF4), ), ), + Text( + delivery.advance, + style: const TextStyle( + fontSize: 12, + color: Color(0xFF646566), + ), + ), ], ), - ), - ], + const SizedBox(height: 12), + + // Deliveries row with background + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + decoration: BoxDecoration( + color: const Color(0xFFF8F6FF), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + delivery.deliveries, + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Color(0xFF2A2A2A), + ), + ), + Text( + delivery.frequency, + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Color(0xFF7B5AF4), + ), + ), + ], + ), + ), + ], + ), ), ); } diff --git a/lib/plans/plan_details.dart b/lib/plans/plan_details.dart new file mode 100644 index 0000000..4ce608b --- /dev/null +++ b/lib/plans/plan_details.dart @@ -0,0 +1,310 @@ +import 'package:flutter/material.dart'; +import '../common/settings.dart'; + +class PlanDetails extends StatefulWidget { + const PlanDetails({super.key}); + + @override + State createState() => _PlanDetailsState(); +} + +class _PlanDetailsState extends State { + final List> deliveries = [ + { + "status": "Pending", + "quantity": "10,000 L", + "type": "Drinking water", + "time": "12:30 AM, Tomorrow", + "button": "Assign Tanker", + }, + { + "status": "Pending", + "quantity": "10,000 L", + "type": "Drinking water", + "time": "12:30 AM, Tomorrow", + "driver": "TS 04 J 8394", + "button": "Assign Driver", + }, + ]; + + Widget _buildInfoCard(String value, String label, {Color? color}) { + return Expanded( + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: color ?? const Color(0xFFF7F7F7), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Text(value, style: fontTextStyle(18, Colors.black, FontWeight.w600)), + const SizedBox(height: 4), + Text(label, style: fontTextStyle(13, Colors.black54, FontWeight.w400)), + ], + ), + ), + ); + } + + Widget _buildFilterChip(String label) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + border: Border.all(color: const Color(0xFFE0E0E0)), + borderRadius: BorderRadius.circular(16), + color: Colors.white, + ), + child: Text( + label, + style: fontTextStyle(13, Colors.black87, FontWeight.w500), + ), + ), + ); + } + + Widget _buildDeliveryCard(Map delivery) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: const Color(0xFFE0E0E0)), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFFFFF2E0), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + delivery['status'], + style: fontTextStyle( + 12, const Color(0xFFE6882C), FontWeight.w500), + ), + ), + const Spacer(), + Text(delivery['time'], + style: fontTextStyle(12, Colors.black54, FontWeight.w400)), + ], + ), + const SizedBox(height: 8), + Text("${delivery['quantity']} - ${delivery['type']}", + style: fontTextStyle(14, Colors.black, FontWeight.w600)), + const SizedBox(height: 8), + if (delivery.containsKey('driver')) + Row( + children: [ + const Icon(Icons.local_shipping_outlined, + color: Color(0xFF8270DB), size: 18), + const SizedBox(width: 6), + Text( + delivery['driver'], + style: fontTextStyle(13, Colors.black87, FontWeight.w500), + ) + ], + ), + const SizedBox(height: 8), + Align( + alignment: Alignment.centerRight, + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF8270DB), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + padding: + const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + ), + child: Text( + delivery['button'], + style: fontTextStyle(13, Colors.white, FontWeight.w600), + ), + ), + ) + ], + ), + ); + } + + @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_new, color: Colors.black), + onPressed: () => Navigator.pop(context), + ), + title: Text("Green Valley Apartments", + style: fontTextStyle(16, Colors.black, FontWeight.w600)), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Image with status + Stack( + children: [ + Image.asset( + 'images/building.png', // replace with your asset + height: 180, + width: double.infinity, + fit: BoxFit.cover, + ), + Positioned( + top: 16, + left: 16, + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: Colors.green.withOpacity(0.9), + borderRadius: BorderRadius.circular(20), + ), + child: Text("Active", + style: fontTextStyle(12, Colors.white, FontWeight.w600)), + ), + ), + ], + ), + + const SizedBox(height: 12), + + // Apartment Info + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + children: [ + Text("Green Valley Apartments", + style: fontTextStyle(18, Colors.black, FontWeight.w600)), + const SizedBox(height: 4), + Text("Gacchibowli, Hyderabad", + style: fontTextStyle(14, Colors.black54, FontWeight.w400)), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.water_drop, color: Color(0xFF8270DB), size: 18), + const SizedBox(width: 4), + Text("Drinking Water", + style: fontTextStyle(14, const Color(0xFF8270DB), FontWeight.w500)), + ], + ), + const SizedBox(height: 4), + Text("25 June 2025 β€’ 24 deliveries", + style: fontTextStyle(13, Colors.black54, FontWeight.w400)), + ], + ), + ), + + const SizedBox(height: 16), + + // Quantity & Balance + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + _buildInfoCard("10K", "Quantity"), + const SizedBox(width: 12), + _buildInfoCard("24k", "Balance", color: const Color(0xFFEFF8F1)), + ], + ), + ), + + const SizedBox(height: 16), + + // Schedule | Pending | Rescheduled + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + _buildInfoCard("3/week", "Schedule"), + const SizedBox(width: 12), + _buildInfoCard("14", "Pending"), + const SizedBox(width: 12), + _buildInfoCard("2", "Rescheduled"), + ], + ), + ), + + const SizedBox(height: 20), + + // Filter chips + SingleChildScrollView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + _buildFilterChip("Status"), + _buildFilterChip("Date"), + _buildFilterChip("Quantity"), + _buildFilterChip("Water Type"), + ], + ), + ), + + const SizedBox(height: 16), + + // Delivery Cards + ...deliveries.map((d) => _buildDeliveryCard(d)).toList(), + + const SizedBox(height: 24), + + // Bottom Buttons + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () {}, + style: OutlinedButton.styleFrom( + side: const BorderSide(color: Color(0xFF8270DB)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + padding: const EdgeInsets.symmetric(vertical: 14), + ), + child: Text("Edit Plan", + style: fontTextStyle( + 14, const Color(0xFF8270DB), FontWeight.w600)), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFE2483D), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + padding: const EdgeInsets.symmetric(vertical: 14), + ), + child: Text("Discontinue", + style: fontTextStyle( + 14, Colors.white, FontWeight.w600)), + ), + ), + ], + ), + ), + const SizedBox(height: 24), + ], + ), + ), + ); + } +} diff --git a/lib/resources/driver_details.dart b/lib/resources/driver_details.dart new file mode 100644 index 0000000..d243dd3 --- /dev/null +++ b/lib/resources/driver_details.dart @@ -0,0 +1,311 @@ +import 'package:flutter/material.dart'; + +class DriverDetailsPage extends StatefulWidget { + var driverDetails; + var status; + DriverDetailsPage({this.driverDetails, this.status}); + + @override + State createState() => _DriverDetailsPageState(); +} + +class _DriverDetailsPageState 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.driverDetails.driver_name.isNotEmpty + ? widget.driverDetails.driver_name[0].toUpperCase() + + widget.driverDetails.driver_name.substring(1) + : '', + style: TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + centerTitle: false, + actions: [ + TextButton( + onPressed: () {}, + child: const Text( + "HELP", + style: TextStyle( + color: Color(0xFF4F46E5), + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + ), + ], + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // πŸ‘€ Driver Profile Card + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFF4F0FF), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CircleAvatar( + radius: 30, + backgroundColor: Colors.grey, + backgroundImage: AssetImage('images/avatar.png'), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status Chip + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: const Color(0xFFE8FFF0), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0xFF0A9E04), width: 0.8), + ), + child: const Text( + "available", + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: Color(0xFF0A9E04), + ), + ), + ), + const SizedBox(height: 4), + Text( + widget.driverDetails.driver_name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + const SizedBox(height: 2), + const Text( + "+91 789456212 β€’ +91 789456212", + style: TextStyle( + fontSize: 13, + color: Colors.black54, + ), + ), + ], + ), + ), + IconButton( + onPressed: () { + // πŸ“ž Call action + }, + icon: const Icon(Icons.call, color: Color(0xFF4F46E5)), + ) + ], + ), + const SizedBox(height: 16), + + // Buttons + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () {}, + style: OutlinedButton.styleFrom( + side: const BorderSide(color: Color(0xFF4F46E5)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + child: const Text( + "View Schedule", + style: TextStyle( + color: Color(0xFF4F46E5), + fontWeight: FontWeight.w600, + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF4F46E5), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + child: const Text( + "Assign", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + ], + ), + ), + + const SizedBox(height: 24), + + // πŸͺͺ License Card + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Container( + width: double.infinity, + decoration: const BoxDecoration( + color: Colors.black, + ), + child: Stack( + children: [ + // background pattern + /*Image.asset( + 'images/license_bg.png', + fit: BoxFit.cover, + width: double.infinity, + height: 140, + ),*/ + Container( + height: 140, + width: double.infinity, + padding: const EdgeInsets.all(16), + color: Colors.black.withOpacity(0.3), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "DRIVING LICENSE", + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + Spacer(), + Text( + widget.driverDetails.driver_name, + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + Text( + "TS84996859930326", + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + Align( + alignment: Alignment.bottomRight, + child: Text( + "Expires on 29/02/2028", + style: TextStyle( + color: Colors.white70, + fontSize: 12, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 24), + + const Text( + "RECENT TRIPS", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + const SizedBox(height: 12), + + _buildTripCard("Drinking Water - 10,000 L", "7:02 PM, 28 Jun 2025"), + const SizedBox(height: 8), + _buildTripCard("Drinking Water - 10,000 L", "7:02 PM, 28 Jun 2025"), + const SizedBox(height: 8), + _buildTripCard("Drinking Water - 10,000 L", "7:02 PM, 28 Jun 2025"), + + const SizedBox(height: 30), + ], + ), + ), + ), + ); + } + + Widget _buildTripCard(String title, String time) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFF8F8F8), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + const Icon(Icons.local_shipping, color: Color(0xFF4F46E5), size: 24), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + const SizedBox(height: 2), + Text( + time, + style: const TextStyle( + fontSize: 12, + color: Colors.black54, + ), + ), + ], + ), + ), + ], + ), + ); + } +} + diff --git a/lib/resources/resources_drivers.dart b/lib/resources/resources_drivers.dart index 50815d7..71c17ef 100644 --- a/lib/resources/resources_drivers.dart +++ b/lib/resources/resources_drivers.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:supplier_new/common/settings.dart'; +import 'package:supplier_new/resources/driver_details.dart'; import 'package:supplier_new/resources/drivers_model.dart'; class FirstCharUppercaseFormatter extends TextInputFormatter { @@ -520,12 +521,22 @@ class _ResourcesDriverScreenState extends State { separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, idx) { final d = driversList[idx]; - return DriverCard( - name: d.driver_name, - status: d.status, - location: d.address, - deliveries: int.tryParse(d.deliveries) ?? 0, - commission: d.commision, + return GestureDetector( + onTap: (){ + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DriverDetailsPage(driverDetails: d), + ), + ); + }, + child: DriverCard( + name: d.driver_name, + status: d.status, + location: d.address, + deliveries: int.tryParse(d.deliveries) ?? 0, + commission: d.commision, + ), ); }, ), diff --git a/lib/resources/resources_fleet.dart b/lib/resources/resources_fleet.dart index a32e38b..2830477 100644 --- a/lib/resources/resources_fleet.dart +++ b/lib/resources/resources_fleet.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:supplier_new/common/settings.dart'; +import 'package:supplier_new/resources/tanker_details.dart'; import 'package:supplier_new/resources/tankers_model.dart'; import 'fleet.dart'; import 'resources_drivers.dart'; @@ -568,13 +569,23 @@ class _ResourcesFleetScreenState extends State { separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, idx) { final it = filtered[idx]; - return TankCard( - title: it.tanker_name, - subtitle: it.type_of_water, - capacity: it.capacity, - code: it.license_plate, - owner: it.supplier_name, - status: List.from(it.availability), + return GestureDetector( + onTap: (){ + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TankerDetailsPage(tankerDetails: it), + ), + ); + }, + child: TankCard( + title: it.tanker_name, + subtitle: it.type_of_water, + capacity: it.capacity, + code: it.license_plate, + owner: it.supplier_name, + status: List.from(it.availability), + ), ); }, ), diff --git a/lib/resources/resources_sources.dart b/lib/resources/resources_sources.dart index ff815a7..c810aa7 100644 --- a/lib/resources/resources_sources.dart +++ b/lib/resources/resources_sources.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/resources/source_loctaions_model.dart'; +import 'package:supplier_new/resources/sourcelocation_details.dart'; import '../google_maps_place_picker_mb/src/models/pick_result.dart'; import '../google_maps_place_picker_mb/src/place_picker.dart'; import 'package:supplier_new/google_maps_place_picker_mb/google_maps_place_picker.dart'; @@ -600,9 +601,19 @@ class _ResourcesSourceScreenState extends State { separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, idx) { final it = filtered[idx]; - return TankCard( - title: it.source_name, - subtitle: it.address, + return GestureDetector( + onTap: (){ + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SourceDetailsScreen(sourceDetails: it), + ), + ); + }, + child: TankCard( + title: it.source_name, + subtitle: it.address, + ), ); }, ), diff --git a/lib/resources/sourcelocation_details.dart b/lib/resources/sourcelocation_details.dart new file mode 100644 index 0000000..6420b3b --- /dev/null +++ b/lib/resources/sourcelocation_details.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../common/settings.dart'; + +class SourceDetailsScreen extends StatefulWidget { + var sourceDetails; + var status; + SourceDetailsScreen({this.sourceDetails, this.status}); + + @override + State createState() => _SourceDetailsScreenState(); +} + +class _SourceDetailsScreenState extends State { + final List> recentTrips = [ + {"type": "Drinking Water", "liters": "10,000 L", "time": "7:02 PM", "date": "28 Jun 2025"}, + {"type": "Drinking Water", "liters": "10,000 L", "time": "7:02 PM", "date": "28 Jun 2025"}, + {"type": "Drinking Water", "liters": "10,000 L", "time": "7:02 PM", "date": "28 Jun 2025"}, + ]; + + void _showMenu() { + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.edit, color: Colors.black), + title: const Text('Edit'), + onTap: () => Navigator.pop(context), + ), + ListTile( + leading: const Icon(Icons.block, color: Colors.black), + title: const Text('Disable'), + onTap: () => Navigator.pop(context), + ), + ListTile( + leading: const Icon(Icons.delete_outline, color: Colors.red), + title: const Text('Delete', style: TextStyle(color: Colors.red)), + onTap: () => Navigator.pop(context), + ), + ], + ), + ); + }, + ); + } + + @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_new, color: Colors.black), + onPressed: () => Navigator.pop(context), + ), + title: Text( widget.sourceDetails.source_name.isNotEmpty + ? widget.sourceDetails.source_name[0].toUpperCase() + + widget.sourceDetails.source_name.substring(1) + : '', + style: fontTextStyle(16, Colors.black, FontWeight.w600)), + actions: [ + TextButton( + onPressed: () {}, + child: Text("HELP", + style: fontTextStyle(12, const Color(0xFF8270DB), FontWeight.w600)), + ), + ], + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Image + Stack( + children: [ + ClipRRect( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8)), + child: Image.asset( + 'images/tanker_image.jpeg', // Replace with your image + height: 200, + width: double.infinity, + fit: BoxFit.cover, + ), + ), + Positioned( + top: 8, + right: 8, + child: IconButton( + icon: const Icon(Icons.more_vert, color: Colors.white), + onPressed: _showMenu, + ), + ) + ], + ), + + const SizedBox(height: 12), + + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.sourceDetails.source_name, + style: fontTextStyle(18, Colors.black, FontWeight.w600)), + const SizedBox(height: 4), + Text("Drinking Water", + style: fontTextStyle(14, const Color(0xFF8270DB), FontWeight.w500)), + const SizedBox(height: 4), + Text("+91 789456212", + style: fontTextStyle(14, Colors.black, FontWeight.w400)), + const SizedBox(height: 4), + Text( + "Road No. 12, Krishnadwar Layout,\nGandipet, Hyderabad, 500065", + style: fontTextStyle(13, const Color(0xFF656565), FontWeight.w400), + ), + ], + ), + ), + + const SizedBox(height: 20), + + // Filling & Wait time cards + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFF7F7F7), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Text("12", + style: fontTextStyle(20, Colors.black, FontWeight.w600)), + Text("sec/L", + style: fontTextStyle(14, Colors.black, FontWeight.w400)), + const SizedBox(height: 4), + Text("Filling Time", + style: fontTextStyle(12, Colors.grey, FontWeight.w400)), + ], + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFF7F7F7), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Text("24", + style: fontTextStyle(20, Colors.black, FontWeight.w600)), + Text("min", + style: fontTextStyle(14, Colors.black, FontWeight.w400)), + const SizedBox(height: 4), + Text("Wait Time", + style: fontTextStyle(12, Colors.grey, FontWeight.w400)), + ], + ), + ), + ), + ], + ), + ), + + const SizedBox(height: 20), + + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text("Recent Trips", + style: fontTextStyle(16, Colors.black, FontWeight.w600)), + ), + const SizedBox(height: 8), + + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: recentTrips.length, + itemBuilder: (context, index) { + final trip = recentTrips[index]; + return Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 6), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFF7F7F7), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + const Icon(Icons.water_drop, color: Color(0xFF8270DB)), + const SizedBox(width: 12), + Expanded( + child: Text( + "${trip['type']} - ${trip['liters']}", + style: fontTextStyle( + 14, const Color(0xFF2D2E30), FontWeight.w500), + ), + ), + Text( + "${trip['time']}, ${trip['date']}", + style: fontTextStyle( + 12, const Color(0xFF656565), FontWeight.w400), + ), + ], + ), + ), + ); + }, + ), + const SizedBox(height: 20), + ], + ), + ), + ); + } +} diff --git a/lib/resources/tanker_details.dart b/lib/resources/tanker_details.dart new file mode 100644 index 0000000..1a02634 --- /dev/null +++ b/lib/resources/tanker_details.dart @@ -0,0 +1,281 @@ +import 'package:flutter/material.dart'; + +class TankerDetailsPage extends StatefulWidget { + var tankerDetails; + var status; + TankerDetailsPage({this.tankerDetails, this.status}); + @override + State createState() => _TankerDetailsPageState(); +} + +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, + ), + ), + centerTitle: false, + actions: [ + TextButton( + onPressed: () {}, + child: const Text( + "HELP", + style: TextStyle( + color: Color(0xFF4F46E5), + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + ), + ], + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // πŸ›» Tanker Image + 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( + children: [ + _buildStatusChip("filled", const Color(0xFF4F46E5)), + const SizedBox(width: 8), + _buildStatusChip("available", const Color(0xFF0A9E04)), + ], + ), + ),*/ + ], + ), + ), + 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( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Ramesh Krishna", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + SizedBox(height: 2), + Text( + widget.tankerDetails.tanker_name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + SizedBox(height: 2), + Text( + widget.tankerDetails.type_of_water+' - '+widget.tankerDetails.capacity+' L', + style: TextStyle( + fontSize: 12, + color: Colors.black54, + ), + ), + ], + ), + ), + 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", + ), + const SizedBox(height: 30), + ], + ), + ), + ), + ); + } + + Widget _buildStatusChip(String label, Color color) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: color), + ), + child: Text( + label, + style: TextStyle( + color: color, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ); + } + + Widget _buildTripCard({ + required String driverName, + required String time, + required String from, + required String to, + }) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFF8F8F8), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Driver + time + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const CircleAvatar( + radius: 14, + backgroundImage: AssetImage('images/avatar.png'), + ), + const SizedBox(width: 8), + Text( + driverName, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 13, + ), + ), + ], + ), + Text( + time, + style: const TextStyle( + fontSize: 11, + color: Colors.black54, + ), + ), + ], + ), + const SizedBox(height: 8), + // From location + Row( + children: [ + const Icon(Icons.location_on, color: Color(0xFF4F46E5), size: 16), + const SizedBox(width: 6), + Expanded( + child: Text( + from, + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ), + ], + ), + const SizedBox(height: 4), + // To location + Row( + children: [ + const Icon(Icons.location_on, color: Colors.red, size: 16), + const SizedBox(width: 6), + Expanded( + child: Text( + to, + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ), + ], + ), + ], + ), + ); + } + +} + + diff --git a/lib/resources/tankers_model.dart b/lib/resources/tankers_model.dart index f92b2b8..0b9a9ef 100644 --- a/lib/resources/tankers_model.dart +++ b/lib/resources/tankers_model.dart @@ -3,6 +3,8 @@ class TankersModel { String address = ''; String type_of_water = ''; String capacity = ''; + String price = ''; + String delivery_fee = ''; String dbId = ''; String status=''; String license_plate=''; @@ -20,6 +22,8 @@ class TankersModel { rtvm.capacity = json['capacity'] ?? ''; rtvm.license_plate = json['license_plate'] ?? ''; rtvm.supplier_name = json['supplier_name'] ?? ''; + rtvm.price = json['price'] ?? ''; + rtvm.delivery_fee = json['delivery_fee'] ?? ''; return rtvm; } diff --git a/lib/set_rates/set_rates.dart b/lib/set_rates/set_rates.dart index d5b4204..52705df 100644 --- a/lib/set_rates/set_rates.dart +++ b/lib/set_rates/set_rates.dart @@ -1,6 +1,10 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:supplier_new/common/settings.dart'; +import '../resources/tankers_model.dart'; + class SetRatesScreen extends StatefulWidget { const SetRatesScreen({super.key}); @@ -16,52 +20,95 @@ class _SetRatesScreenState extends State // Define categories + tankers final Map> tankerGroups = {}; final Map deliveryControllers = {}; - - /*delivery fee controllers*/ final TextEditingController pumpFeeController = TextEditingController(); + List tankersList = []; + bool isLoading = false; - final List> data = [ - {"category": "DRINKING WATER", "size": "5,000L", "price": "12"}, - {"category": "DRINKING WATER", "size": "15,000L", "price": "18"}, - {"category": "DRINKING WATER", "size": "20,000L", "price": "20"}, - {"category": "BORE WATER", "size": "5,000L", "price": "10"}, - {"category": "BORE WATER", "size": "10,000L", "price": "14"}, - {"category": "BORE WATER", "size": "15,000L", "price": "16"}, - {"category": "BORE WATER", "size": "25,000L", "price": null}, - ]; + Future _fetchTankers() async { + setState(() => isLoading = true); - @override - void initState() { - super.initState(); - _tabController = TabController(length: 3, vsync: this); + try { + final response = await AppSettings.getTankers(); + final data = (jsonDecode(response)['data'] as List) + .map((e) => TankersModel.fromJson(e)) + .toList(); + + if (!mounted) return; + + setState(() { + // 🧹 Clear old data before adding new + tankersList.clear(); + tankerGroups.clear(); + controllers.clear(); + deliveryControllers.clear(); - // Group data by category and initialize controllers for WaterCharges - for (final item in data) { - final category = item['category'] as String; - final size = item['size'] as String; - final price = item['price']?.toString() ?? ""; // default to empty if null + // πŸ†• Assign new data + tankersList = data; + isLoading = false; - if (!tankerGroups.containsKey(category)) { - tankerGroups[category] = []; - } - tankerGroups[category]!.add(size); + // 🧭 Group data and create controllers + for (final item in data) { + final category = item.type_of_water; + final size = item.capacity; + final price = item.price.toString(); - // Pre-fill controller with price or empty - controllers.putIfAbsent("$category-$size", () => TextEditingController(text: price)); + if (!tankerGroups.containsKey(category)) { + tankerGroups[category] = []; + } + + // avoid duplicate sizes for same category + if (!tankerGroups[category]!.contains(size)) { + tankerGroups[category]!.add(size); + } + + final controllerKey = "$category-$size"; + controllers[controllerKey] = TextEditingController(text: price); + } + final capacities = tankersList.map((t) => normalizeCap(t.capacity)).toSet(); + for (final cap in capacities) { + // take the first tanker of this capacity to prefill (optional) + final matched = tankersList.firstWhere( + (t) => normalizeCap(t.capacity) == cap, + orElse: () => TankersModel(), + ); + final deliveryFee = matched.delivery_fee.toString() ?? ""; + deliveryControllers.putIfAbsent( + cap, + () => TextEditingController(text: deliveryFee), + ); + } + }); + } catch (e) { + debugPrint("⚠️ Error fetching tankers: $e"); + setState(() => isLoading = false); } + } + + Future _fetchPumpFee() async { + + try { + final response = await AppSettings.getSupplierDetails(); + final data = jsonDecode(response); + + if (!mounted) return; + + setState(() { + pumpFeeController.text=data['pumping_fee']; + }); + } catch (e) { + debugPrint("⚠️ Error fetching tankers: $e"); - // Initialize controllers for unique capacities for DeliveryFee - final capacities = data.map((item) => item['size'] as String).toSet(); - for (final cap in capacities) { - // Take first price for this capacity, default to empty if null - final price = data.firstWhere( - (d) => d['size'] == cap, - orElse: () => {'price': ''}, - )['price']?.toString() ?? ""; - deliveryControllers.putIfAbsent(cap, () => TextEditingController(text: price)); } + } + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 3, vsync: this); + _fetchTankers(); + _fetchPumpFee(); } @override @@ -126,24 +173,35 @@ class _SetRatesScreenState extends State } Widget WaterCharges() { - return SingleChildScrollView( + return isLoading + ? const Center(child: CircularProgressIndicator()):SingleChildScrollView( padding: const EdgeInsets.all(16), - child: Column( + child: tankersList.isEmpty?Center( + child: Text( + 'No Data Available', + style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700), + ), + ) + :Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ for (final entry in tankerGroups.entries) ...[ Text( entry.key, - style: fontTextStyle(10, Color(0XFF2D2E30), FontWeight.w600), + style: + fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600), ), const SizedBox(height: 8), for (final size in entry.value) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - labelText("$size (in β‚Ή)*"), + labelText("$size L (in β‚Ή)*"), const SizedBox(height: 4), - buildTextField("β‚Ή500", controllers["${entry.key}-$size"]!), + buildTextField( + "β‚Ή500", + controllers["${entry.key}-$size"]!, + ), const SizedBox(height: 12), ], ), @@ -153,32 +211,63 @@ class _SetRatesScreenState extends State width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Color(0XFF8270DB), - foregroundColor: Color(0XFFFFFFFF), - padding: EdgeInsets.symmetric(vertical: 10), + backgroundColor: const Color(0XFF8270DB), + foregroundColor: const Color(0XFFFFFFFF), + padding: const EdgeInsets.symmetric(vertical: 10), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(24), // <-- set your radius here + borderRadius: BorderRadius.circular(24), ), ), - onPressed: () { - final Map> grouped = {}; + onPressed: () async { + final List> tankersPayload = []; + tankerGroups.forEach((category, sizes) { - grouped[category] = {}; for (final size in sizes) { - grouped[category]![size] = - controllers["$category-$size"]!.text; + final key = "$category-$size"; + final amount = controllers[key]?.text.trim() ?? "0"; + + // find the tanker in tankersList by capacity (size) + final matchedTanker = tankersList.firstWhere( + (t) => t.capacity == size, + orElse: () => TankersModel(), + ); + + final tankerName = matchedTanker.tanker_name.isNotEmpty + ? matchedTanker.tanker_name + : "$category $size L"; // fallback if not found + + tankersPayload.add({ + "tankerName": tankerName, + "amount": amount.isEmpty ? "0" : amount, + }); } }); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Rates saved successfully")), - ); + final payload = { + "price_type": "price", + "tankers": tankersPayload, + }; + + print(payload); - print(grouped); // Debug: print entered rates + try { + final ok = await AppSettings.setRatesDaily(payload); + if (ok) { + AppSettings.longSuccessToast("Prices updated successfully"); + _fetchTankers(); + } else { + AppSettings.longFailedToast("Update failed"); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Error: $e")), + ); + } }, child: Text( "Save", - style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600), + style: + fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600), ), ), ) @@ -187,17 +276,32 @@ class _SetRatesScreenState extends State ); } + String normalizeCap(String cap) => cap.replaceAll(',', '').replaceAll(' ', ''); Widget DeliveryFee() { - // Extract unique capacities - final capacities = data.map((item) => item['size'] as String).toSet().toList(); + // Group by normalized capacity + final Map> capacityGroups = {}; + + for (final t in tankersList) { + final normCap = normalizeCap(t.capacity); + capacityGroups.putIfAbsent(normCap, () => []).add(t); + } + + // Sorted list by numeric capacity (if parseable), else lexicographic + final capacities = capacityGroups.keys.toList() + ..sort((a, b) { + final ai = int.tryParse(a); + final bi = int.tryParse(b); + if (ai != null && bi != null) return ai.compareTo(bi); + return a.compareTo(b); + }); return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + Text( "ADDITIONAL RATES", style: fontTextStyle(10, const Color(0xFF2D2E30), FontWeight.w600), ), @@ -208,11 +312,16 @@ class _SetRatesScreenState extends State ), SizedBox(height: MediaQuery.of(context).size.height * .020), - // πŸ”Ή Dynamic textfields - for (final cap in capacities) ...[ - labelText('$cap tanker (per KM)*'), + // one field per normalized capacity + for (final normCap in capacities) ...[ + // show without commas (normalized already) + labelText('${normCap} L tanker (per KM)*'), SizedBox(height: MediaQuery.of(context).size.height * .004), - buildTextField("+ β‚Ή12", deliveryControllers[cap]!), + buildTextField( + "+ β‚Ή12", + // controller keyed by normalized cap + deliveryControllers[normCap] ?? TextEditingController(), + ), const SizedBox(height: 12), ], @@ -221,37 +330,66 @@ class _SetRatesScreenState extends State width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Color(0XFF8270DB), - foregroundColor: Color(0XFFFFFFFF), - padding: EdgeInsets.symmetric(vertical: 10), + backgroundColor: const Color(0XFF8270DB), + foregroundColor: const Color(0XFFFFFFFF), + padding: const EdgeInsets.symmetric(vertical: 10), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(24), // <-- set your radius here + borderRadius: BorderRadius.circular(24), ), ), - onPressed: () { - final Map fees = {}; - deliveryControllers.forEach((cap, controller) { - fees[cap] = controller.text; + onPressed: () async { + final List> tankersPayload = []; + + // loop groups; use the same fee for all tankers in the group + capacityGroups.forEach((normCap, list) { + final fee = (deliveryControllers[normCap]?.text ?? "").trim(); + final amt = fee.isEmpty ? "0" : fee; + + for (final t in list) { + final tankerName = t.tanker_name.isNotEmpty + ? t.tanker_name + : "Tanker ${normalizeCap(t.capacity)} L"; + tankersPayload.add({ + "tankerName": tankerName, + "amount": amt, + }); + } }); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Rates saved successfully")), - ); + final payload = { + "price_type": "delivery_fee", + "tankers": tankersPayload, + }; + + print(payload); - print(fees); + try { + final ok = await AppSettings.setRatesDaily(payload); + if (ok) { + AppSettings.longSuccessToast("Delivery fees updated successfully"); + _fetchTankers(); + } else { + AppSettings.longFailedToast("Update failed"); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Error: $e")), + ); + } }, child: Text( "Save", style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600), ), ), - ) + ), ], ), ); } + Widget PumpFee() { return SingleChildScrollView( padding: const EdgeInsets.all(16), @@ -278,18 +416,32 @@ class _SetRatesScreenState extends State foregroundColor: Color(0XFFFFFFFF), padding: EdgeInsets.symmetric(vertical: 10), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(24), // <-- set your radius here + borderRadius: + BorderRadius.circular(24), // <-- set your radius here ), ), - onPressed: () { - // Handle save logic - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Rates saved successfully")), - ); + onPressed: () async{ + var payload = new Map(); + payload["pumping_fee"] = pumpFeeController.text.toString(); + + bool tankStatus = await AppSettings.updatePumpFee(payload); + + try { + if (tankStatus) { + AppSettings.longSuccessToast("Pump fee Updated Successfully"); + _fetchPumpFee(); + } + else { + AppSettings.longFailedToast("Pump fee updation failed"); + } + } catch (exception) { + print(exception); + } }, child: Text( "Save", - style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600), + style: + fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600), ), ), ) @@ -317,9 +469,10 @@ class _SetRatesScreenState extends State child: Column( children: [ SizedBox(height: MediaQuery.of(context).size.height * .096), - const CircleAvatar(radius: 50, backgroundColor: Color(0XFFC9C2F0)), + const CircleAvatar( + radius: 50, backgroundColor: Color(0XFFC9C2F0)), SizedBox(height: MediaQuery.of(context).size.height * .016), - Text( + Text( "What’s today’s water price?", style: fontTextStyle(20, Color(0XFF343637), FontWeight.w600), ), @@ -344,28 +497,33 @@ class _SetRatesScreenState extends State indicatorColor: Colors.transparent, // remove underline dividerColor: Colors.transparent, isScrollable: false, - overlayColor: MaterialStateProperty.all(Colors.transparent),// equal width + overlayColor: MaterialStateProperty.all( + Colors.transparent), // equal width tabs: List.generate(3, (index) { - final labels = ['Water Type', 'Delivery Fee','Pump']; + final labels = ['Water Type', 'Delivery Fee', 'Pump']; final isSelected = _tabController.index == index; return Container( decoration: BoxDecoration( - color: isSelected ? const Color(0XFFF1F1F1) : Colors.transparent, + color: isSelected + ? const Color(0XFFF1F1F1) + : Colors.transparent, borderRadius: BorderRadius.circular(27), ), alignment: Alignment.center, child: Text( labels[index], - style: isSelected ?fontTextStyle( - 12, - const Color(0XFF101214), - FontWeight.w600, - ):fontTextStyle( - 12, - const Color(0XFF646464), - FontWeight.w600, - ), + style: isSelected + ? fontTextStyle( + 12, + const Color(0XFF101214), + FontWeight.w600, + ) + : fontTextStyle( + 12, + const Color(0XFF646464), + FontWeight.w600, + ), ), ); }),