From dfc47308d6960e758c6d88e2acd508018c5d2675 Mon Sep 17 00:00:00 2001 From: Sneha Date: Tue, 21 Oct 2025 13:01:12 +0530 Subject: [PATCH] plans page changes --- lib/common/dashboard.dart | 2 +- lib/common/settings.dart | 25 ++ lib/orders/assign_driver.dart | 401 ++++++++++++++++- lib/plans/accept_plan_requests.dart | 669 ++++++++++++++++++++++++++++ lib/plans/all_plans.dart | 2 +- lib/plans/edit_plan_requests.dart | 446 ++++++++++++++----- lib/plans/plan_requests.dart | 486 +++++++++++--------- lib/plans/plan_requests_model.dart | 87 ++++ 8 files changed, 1796 insertions(+), 322 deletions(-) create mode 100644 lib/plans/accept_plan_requests.dart create mode 100644 lib/plans/plan_requests_model.dart diff --git a/lib/common/dashboard.dart b/lib/common/dashboard.dart index 4b51946..a26757b 100644 --- a/lib/common/dashboard.dart +++ b/lib/common/dashboard.dart @@ -934,7 +934,7 @@ class _HomeScreenState extends State { Navigator.push( context, new MaterialPageRoute( - builder: (__) => new PlanRequestPage())); + builder: (__) => new PlanRequestsPage())); }, child: RequestCard( title: "Plan Requests", diff --git a/lib/common/settings.dart b/lib/common/settings.dart index a1d764f..6191772 100644 --- a/lib/common/settings.dart +++ b/lib/common/settings.dart @@ -154,6 +154,7 @@ class AppSettings{ static String getOrderRequestsFromUsersUrl = host + 'getuserRequestbookingsforsupplier'; static String acceptOrderRequestsUrl = host + 'request-booking-with-charges'; static String getAcceptedOrdersFromUsersUrl = host + 'supplier/booking/advance-paid'; + static String getPlanRequestsFromUsersUrl = host + 'getuserRequestbookingsforplansforsupplier'; static String getTankersUrl = host + 'getTankers'; static String addTankerUrl = host + 'addTankers'; static String getDriversUrl = host + 'getalldeliveryboys'; @@ -460,6 +461,30 @@ class AppSettings{ } } + static Future getPlanRequestsFromUsers() async { + var uri = Uri.parse(getPlanRequestsFromUsersUrl+'/'+supplierId); + //uri = uri.replace(query: 'customerId=$customerId'); + + var response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else if (response.statusCode == 401) { + bool status = await AppSettings.resetToken(); + if (status) { + response = await http.get(uri, headers: await buildRequestHeaders()); + if (response.statusCode == 200) { + return response.body; + } else { + return ''; + } + } else { + return ''; + } + } else { + return ''; + } + } + static Future getTankers() async { var uri = Uri.parse(getTankersUrl); uri = uri.replace(query: 'supplierId=$supplierId'); diff --git a/lib/orders/assign_driver.dart b/lib/orders/assign_driver.dart index 8841c69..cfb0bb0 100644 --- a/lib/orders/assign_driver.dart +++ b/lib/orders/assign_driver.dart @@ -1,10 +1,8 @@ import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:supplier_new/common/settings.dart'; -import 'package:supplier_new/orders/edit_order_requests.dart'; - import '../resources/drivers_model.dart'; +import '../resources/tankers_model.dart'; class AssignDriverScreen extends StatefulWidget { var order; @@ -20,12 +18,15 @@ class _AssignDriverScreenState extends State { double amountToPayAfterDelivery = 0.0; double totalFare = 0.0; bool isLoading = false; + bool isTankersDataLoading = false; List driversList = []; + List tankersList = []; @override void initState() { // TODO: implement initState super.initState(); + _fetchTankers(); _fetchDrivers(); advance = 150; advancePayable = advance; @@ -51,6 +52,26 @@ class _AssignDriverScreenState extends State { } } + Future _fetchTankers() async { + setState(() => isTankersDataLoading = true); + try { + final response = await AppSettings.getTankers(); + final data = (jsonDecode(response)['data'] as List) + .map((e) => TankersModel.fromJson(e)) + .toList(); + if (!mounted) return; + setState(() { + tankersList = data; + isTankersDataLoading = false; + }); + } catch (e) { + debugPrint("⚠️ Error fetching tankers: $e"); + setState(() => isTankersDataLoading = false); + } + } + + + void _showAssignDriverBottomSheet() { int? selectedDriverIndex; // ✅ Track selected driver @@ -293,9 +314,243 @@ class _AssignDriverScreenState extends State { ); } + void _showAssignTankerBottomSheet() { + int? selectedTankerIndex; // ✅ Track selected tanker + + // 🔹 Helper function to normalize capacity values + int _capToLiters(dynamic cap) { + if (cap == null) return -1; + if (cap is num) return cap.round(); + final s = cap.toString().toLowerCase().replaceAll(',', '').trim(); + final match = RegExp(r'(\d+(\.\d+)?)').firstMatch(s); + if (match == null) return -1; + final n = double.tryParse(match.group(1)!) ?? -1; + if (n < 0) return -1; + // If string contains "kl" -> convert KL to Liters + if (s.contains('kl')) return (n * 1000).round(); + + // Otherwise already in liters + return n.round(); + } + + showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + enableDrag: false, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (context) { + return WillPopScope( + onWillPop: () async => false, + child: StatefulBuilder( + builder: (context, setModalState) { + return DraggableScrollableSheet( + expand: false, + initialChildSize: 0.7, + minChildSize: 0.5, + maxChildSize: 0.9, + builder: (context, scrollController) { + final requiredLiters = _capToLiters(widget.order.capacity); + final filteredTankers = (tankersList ?? []) + .where((t) => _capToLiters(t.capacity) == requiredLiters) + .toList(); + + return Container( + padding: const EdgeInsets.all(16), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 60, + height: 4, + margin: const EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: const Color(0xFFE0E0E0), + borderRadius: BorderRadius.circular(2), + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Assign Tanker", + style: fontTextStyle(16, const Color(0XFF2A2A2A), FontWeight.w600), + ), + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Image.asset( + 'images/cross.png', + height: 24, + width: 24, + color: const Color(0XFF2A2A2A), + ), + ) + ], + ), + const SizedBox(height: 16), + + // 🏢 Order Info + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + border: Border.all(color: const Color(0XFFC9C2F0), width: 0.5), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.order.building_name, + style: fontTextStyle(20, const Color(0XFF2D2E30), FontWeight.w600), + ), + Text( + widget.order.displayAddress, + style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + const Spacer(), + Text( + '${widget.order.distanceInKm} Km', + style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + ), + const SizedBox(height: 16), + + Text( + "SELECT TANKER", + style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600), + ), + const SizedBox(height: 12), + + // 🧍 Tanker List + Expanded( + child: isTankersDataLoading + ? const Center(child: CircularProgressIndicator()) + : filteredTankers.isEmpty + ? Center( + child: Text( + 'No data available for capacity ${widget.order.capacity}', + style: fontTextStyle(12, const Color(0xFF939495), FontWeight.w500), + ), + ) + : ListView.separated( + controller: scrollController, + itemCount: filteredTankers.length, + separatorBuilder: (_, __) => const SizedBox(height: 12), + itemBuilder: (context, idx) { + final d = filteredTankers[idx]; + final bool isSelected = selectedTankerIndex == idx; + + return GestureDetector( + onTap: () { + setModalState(() { + selectedTankerIndex = idx; + }); + }, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide( + color: isSelected + ? const Color(0XFF8270DB) + : const Color(0XFFC3C4C4), + width: 1, + ), + ), + color: isSelected + ? const Color(0XFFEDEBFF) + : Colors.white, + child: Padding( + padding: const EdgeInsets.all(0.0), + child: TankersCard( + title: d.tanker_name, + subtitle: d.type_of_water, + capacity: d.capacity, + code: d.license_plate, + owner: d.supplier_name, + status: List.from(d.availability), + ), + ), + ), + ); + }, + ), + ), + + const SizedBox(height: 16), + + // 🟣 Assign Button + SizedBox( + width: double.infinity, + child: GestureDetector( + onTap: () async { + if (selectedTankerIndex == null) { + AppSettings.longFailedToast('Please select tanker'); + return; + } + + if (filteredTankers.isEmpty || + selectedTankerIndex! >= filteredTankers.length) { + AppSettings.longFailedToast('Selected tanker not available'); + return; + } + + final selectedTanker = filteredTankers[selectedTankerIndex!]; + + // ✅ Call your API here + // await _assignTankerApi(selectedTanker.id); + + if (context.mounted) Navigator.pop(context); + }, + child: Container( + decoration: BoxDecoration( + color: const Color(0XFF8270DB), + borderRadius: BorderRadius.circular(24), + ), + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 12), + child: Text( + 'Assign', + style: fontTextStyle( + 14, + const Color(0XFFFFFFFF), + FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ); + }, + ); + }, + ), + ); + }, + ); + } @override Widget build(BuildContext context) { @@ -596,10 +851,10 @@ class _AssignDriverScreenState extends State { padding: EdgeInsets.symmetric(vertical: 10), ), onPressed: () async { - _showAssignDriverBottomSheet(); + _showAssignTankerBottomSheet(); }, child: Text( - "Assign Driver", + "Assign Tanker", style: fontTextStyle( 14, const Color(0XFFFFFFFF), FontWeight.w400), ), @@ -691,3 +946,139 @@ class _AssignDriverScreenState extends State { ); } } + +// ====== TankersCard ====== +class TankersCard extends StatelessWidget { + final String title; + final String subtitle; + final String capacity; + final String code; + final String owner; + final List status; + + const TankersCard({ + super.key, + required this.title, + required this.subtitle, + required this.capacity, + required this.code, + required this.owner, + required this.status, + }); + + Color _chipColor(String s) { + switch (s) { + case 'filled': + return const Color(0xFFFFFFFF); + case 'available': + return const Color(0xFFE8F0FF); + case 'empty': + return const Color(0xFFFFEEEE); + case 'in-use': + return const Color(0xFFFFF0E6); + case 'maintenance': + return const Color(0xFFFFF4E6); + default: + return const Color(0xFFECECEC); + } + } + + Color _chipTextColor(String s) { + switch (s) { + case 'filled': + return const Color(0xFF1D7AFC); + case 'available': + return const Color(0xFF0A9E04); + case 'empty': + return const Color(0xFFE2483D); + case 'in-use': + return const Color(0xFFEA843B); + case 'maintenance': + return const Color(0xFFD0AE3C); + default: + return Colors.black87; + } + } + + @override + Widget build(BuildContext context) { + ImageProvider avatarProvider = + (AppSettings.profilePictureUrl != '' && AppSettings.profilePictureUrl != 'null') + ? NetworkImage(AppSettings.profilePictureUrl) + : const AssetImage("images/profile_pic.png") as ImageProvider; + + Widget _statusChip(String s) { + final chipTextColor = _chipTextColor(s); + return Container( + margin: const EdgeInsets.only(left: 6), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: _chipColor(s), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: chipTextColor, width: 1), + ), + child: Text(s, style: fontTextStyle(10, chipTextColor, FontWeight.w400)), + ); + } + + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade200), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// 👉 First line: Avatar + Text + Status + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircleAvatar( + radius: 12, + backgroundImage: avatarProvider, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + "$subtitle • $title", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: fontTextStyle(12, const Color(0xFF343637), FontWeight.w600), + ), + ), + const SizedBox(width: 8), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 160), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + reverse: true, + child: Row( + children: status.map(_statusChip).toList(), + ), + ), + ), + ], + ), + const SizedBox(height: 8), + + /// 👉 Second line: Capacity + Owner + Code + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("$capacity L", style: fontTextStyle(10, const Color(0xFF343637), FontWeight.w600)), + Row( + children: [ + Text(owner, style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400)), + const SizedBox(width: 8), + Text(code, style: fontTextStyle(10, const Color(0xFF515253), FontWeight.w400)), + ], + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/plans/accept_plan_requests.dart b/lib/plans/accept_plan_requests.dart new file mode 100644 index 0000000..2a38f63 --- /dev/null +++ b/lib/plans/accept_plan_requests.dart @@ -0,0 +1,669 @@ +import 'package:flutter/material.dart'; +import 'package:supplier_new/common/settings.dart'; +import 'package:supplier_new/plans/edit_plan_requests.dart'; + +class AcceptPlanRequests extends StatefulWidget { + var order; + var status; + AcceptPlanRequests({this.order, this.status}); + @override + State createState() => _AcceptPlanRequestsState(); +} + +class _AcceptPlanRequestsState extends State { + int advancePayable = 0; + int advance =0; + double amountToPayAfterDelivery = 0.0; + double totalFare = 0.0; + + @override + void initState() { + // TODO: implement initState + super.initState(); + advance = 150; + advancePayable = advance; + //totalFare = advance + double.parse(widget.order.quoted_amount)??0; + amountToPayAfterDelivery = totalFare - advancePayable; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + extendBodyBehindAppBar: true, + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + scrolledUnderElevation: 0, + title: Text( + '', + style: fontTextStyle(16, Color(0XFF2A2A2A), FontWeight.w600), + ), + iconTheme: IconThemeData(color: Color(0XFF2A2A2A)), + actions: [ + Row( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(0, 10, 10, 10), + child: IconButton( + icon: Image.asset( + 'images/help_appbar.png', + height: 20, + width: 20, + color: Color(0XFFFFFFFF), + ), + onPressed: () {}, + ), + ) + ], + ) + ], + leading: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Padding( + padding: + const EdgeInsets.fromLTRB(8, 8, 8, 8), // Add padding if needed + child: Image.asset( + 'images/backbutton_appbar.png', // Replace with your image path + fit: BoxFit.contain, + color: Color(0XFFFFFFFF), + height: 24, + width: 24, + ), + ), + ), + ), + + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// 🔹 Top Card with Image + Stack( + clipBehavior: Clip.none, + children: [ + /// 🔹 Background Image + ClipRRect( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(0), + bottomRight: Radius.circular(0), + ), + child: Image.asset( + "images/building.png", + height: 220, + width: double.infinity, + fit: BoxFit.cover, + ), + ), + + /// 🔹 Floating Info Card (half on image, half below) + Positioned( + bottom: -40, // pulls the card out by 40px + left: 12, + right: 12, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 6, + offset: Offset(0, 3), + ), + ], + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Color(0XFFFFFFFF), + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: widget.status.statusColor, + width: 0.5, + ), + ), + child: Text(widget.status.status, + style: fontTextStyle( + 12, + widget.status.statusColor, + FontWeight.w500)), + ), + Text( + widget.order.building_name, + style: fontTextStyle( + 20, const Color(0XFF2D2E30), FontWeight.w600), + ), + SizedBox(height: 4), + Text( + widget.order.displayAddress, + style: fontTextStyle( + 12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + const Spacer(), + Text( + widget.order.distanceInKm.toString() + 'Km', + style: fontTextStyle( + 12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), + ), + ), + ], + ), + SizedBox( + height: MediaQuery.of(context).size.height * .08, + ), + + /// 🔹 Order Details + Padding( + padding: EdgeInsets.fromLTRB(16, 0, 16, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "PLAN DETAILS", + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .011, + ), + _detailTwoRow( + "Tanker Price", + "₹${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}", + "images/financialsBottomIcon.png", + "", + "", + ""), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Water Type", + "${widget.order.type_of_water}", + "images/water.png", + "Capacity", + "${widget.order.capacity}", + "images/capacity.png", + + ), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Start Date", + "${widget.order.averageTime}", + "images/time.png", + "End Date", + "${widget.order.averageTime}", + "images/advance.png", + ), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Frequency", + "${widget.order.quantity}", + "images/quantity.png", + "", + "", + ""), + + + ], + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .008, + ), + + /// 🔹 Additional Details + Padding( + padding: EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "ADDITIONAL DETAILS", + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .011, + ), + Text( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " + "aliquip ex ea commodo consequat.", + style: fontTextStyle( + 12, const Color(0XFF646566), FontWeight.w400), + ), + ], + )), + + /// 🔹 Payment Summary + Padding( + padding: EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "PAYMENT SUMMARY", + style: fontTextStyle( + 10, const Color(0XFF2D2E30), FontWeight.w600), + ), + SizedBox( + height: MediaQuery.of(context).size.height * .011, + ), + _detailRow("Tanker Price", + "₹${AppSettings.formDouble(widget.order.quoted_amount) ?? ''}"), + SizedBox( + height: MediaQuery.of(context).size.height * .004, + ), + _detailRow("Booking Charges", "₹ " + advance.toString()), + SizedBox( + height: MediaQuery.of(context).size.height * .004, + ), + _detailRow("Total Amount", "₹ " + totalFare.toString()), + SizedBox( + height: MediaQuery.of(context).size.height * .004, + ), + Divider( + color: Color(0XFF646566), + thickness: 0.3, + ), + SizedBox( + height: MediaQuery.of(context).size.height * .004, + ), + _detailRow("Booking Charges Payable", + '₹${AppSettings.formDouble(advancePayable.toString()) ?? ''}'), + SizedBox( + height: MediaQuery.of(context).size.height * .004, + ), + _detailRow("Amount to Pay (After Delivery)", + '₹${AppSettings.formDouble(amountToPayAfterDelivery.toString()) ?? ''}'), + ], + ), + ), + + const SizedBox(height: 80), // space for bottom buttons + ], + ), + ), + + /// 🔹 Bottom Action Buttons + bottomNavigationBar: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Color(0XFFFFFFFF), + border: Border(top: BorderSide(color: Color(0XFFF5F6F6))), + ), + child: Row( + children: [ + Expanded( + child: OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: Color(0XFF000000), + backgroundColor: Color(0xFFF1F1F1), + side: BorderSide(color: Color(0xFFF1F1F1)), + padding: + EdgeInsets.symmetric(vertical: 10), // uniform height + ), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => EditPlanRequests( + order: widget.order, + advance: advance.toString(), + status: widget.status, + ), + ), + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset('images/edit.png', height: 20, width: 20), + SizedBox(width: 4), + Text( + "Edit Plan", + style: fontTextStyle( + 14, const Color(0XFF000000), FontWeight.w400), + ), + ], + ), + ), + ), + SizedBox(width: 8), + Expanded( + child: OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: Color(0XFFE2483D), + backgroundColor: Colors.white, + side: BorderSide(color: Color(0XFFE2483D)), + padding: EdgeInsets.symmetric(vertical: 10), + ), + onPressed: ()async { + /*AppSettings.preLoaderDialog(context); + + bool isOnline = await AppSettings.internetConnectivity(); + + if (isOnline) { + var payload = new Map(); + payload["supplierId"] = AppSettings.supplierId; + payload["amount"] = int.parse(widget.order.quoted_amount); + payload["delivery_charges"] = advance; + payload["action"] = 'reject'; + + bool status = await AppSettings.acceptOrderRequests( + payload, widget.order.dbId); + + try { + if (status) { + Navigator.of(context,rootNavigator: true).pop(); + AppSettings.longSuccessToast("Order request rejected Successfully"); + Navigator.pop(context, true); + } + else{ + Navigator.of(context,rootNavigator: true).pop(); + AppSettings.longFailedToast("reject of order request Failed"); + } + } catch (e) { + Navigator.of(context,rootNavigator: true).pop(); + print(e); + } + } + else{ + Navigator.of(context,rootNavigator: true).pop(); + AppSettings.longFailedToast("Please Check internet"); + }*/ + + showRejectBottomSheet(context); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset('images/cross.png', height: 20, width: 20), + SizedBox(width: 4), + Text( + "Reject", + style: fontTextStyle( + 14, const Color(0XFF000000), FontWeight.w400), + ), + ], + ), + ), + ), + SizedBox(width: 8), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Color(0XFF0A9E04), + foregroundColor: Color(0XFFFFFFFF), + padding: EdgeInsets.symmetric(vertical: 10), + ), + onPressed: () async { + AppSettings.preLoaderDialog(context); + + bool isOnline = await AppSettings.internetConnectivity(); + + if (isOnline) { + var payload = new Map(); + payload["supplierId"] = AppSettings.supplierId; + payload["amount"] = int.parse(widget.order.quoted_amount); + payload["delivery_charges"] = advance; + payload["action"] = 'accept'; + + bool status = await AppSettings.acceptOrderRequests( + payload, widget.order.dbId); + + try { + if (status) { + Navigator.of(context,rootNavigator: true).pop(); + Navigator.pop(context, true); + } + else{ + Navigator.of(context,rootNavigator: true).pop(); + AppSettings.longFailedToast("Accept of order request Failed"); + } + } catch (e) { + Navigator.of(context,rootNavigator: true).pop(); + print(e); + } + } + else{ + Navigator.of(context,rootNavigator: true).pop(); + AppSettings.longFailedToast("Please Check internet"); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset('images/rite.png', height: 20, width: 20), + SizedBox(width: 4), + Text( + "Accept", + style: fontTextStyle( + 14, const Color(0XFFFFFFFF), FontWeight.w400), + ), + ], + ), + ), + ), + ], + )), + ); + } + + /// 🔹 Helper widget for rows + Widget _detailRow(String title, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: fontTextStyle(12, const Color(0XFF646566), FontWeight.w400), + ), + Text( + value, + style: fontTextStyle(12, const Color(0XFF2D2E30), FontWeight.w500), + ), + ], + ), + ); + } + + Widget _detailTwoRow( + String title1, + String value1, + String path1, + String title2, + String value2, + String path2, { + EdgeInsetsGeometry padding = const EdgeInsets.symmetric(vertical: 6), + }) { + final titleStyle = fontTextStyle(12, Color(0XFF646566), FontWeight.w400); + final valueStyle = fontTextStyle(12, Color(0XFF343637), FontWeight.w500); + + Widget _col(String t, String v, String path) { + return Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (path.isNotEmpty) + Image.asset( + path, + fit: BoxFit.contain, + height: 20, + width: 20, + color: const Color(0XFFC3C4C4), + ), + if (path.isNotEmpty) const SizedBox(width: 6), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t, + style: titleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis), + Text(v, + style: valueStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis), + ], + ), + ) + ], + ), + ), + ); + } + + return Padding( + padding: padding, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _col(title1, value1, path1), + _col(title2, value2, path2), + ], + ), + ); + } + + Future showRejectBottomSheet(BuildContext context) { + final reasons = [ + "I got a better deal", + "The price is not reasonable", + "I do not have the capacity to deliver", + "I do not have drivers to deliver", + "Others", + ]; + + String? selectedReason; + + return showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (ctx) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + child: StatefulBuilder( + builder: (context, setState) { + return SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // drag handle + Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey[400], + borderRadius: BorderRadius.circular(2), + ), + ), + ), + const SizedBox(height: 20), + Text( + "REASON FOR REJECTION", + style: fontTextStyle(10, const Color(0XFF2D2E30), FontWeight.w600), + ), + const SizedBox(height: 8), + + // reasons + ...reasons.map( + (reason) => RadioListTile( + contentPadding: EdgeInsets.zero, + title: Text( + reason, + style: fontTextStyle(14, const Color(0XFF2D2E30), FontWeight.w400), + ), + value: reason, + activeColor: const Color(0XFFE2483D), + groupValue: selectedReason, + onChanged: (v) => setState(() => selectedReason = v), + ), + ), + + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () => Navigator.pop(context, null), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + side: const BorderSide(color:Color(0XFFFFFFFF)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + ), + child: Text( + "Cancel", + style: fontTextStyle(14, const Color(0XFF939495), FontWeight.w600), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ElevatedButton( + onPressed: selectedReason == null + ? null + : () => Navigator.pop(context, selectedReason), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFE2483D), + disabledBackgroundColor: Color(0xFFE2483D), + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + ), + child: Text( + "Reject Request", + style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w600), + ), + ), + ), + ], + ), + ], + ), + ); + }, + ), + ); + }, + ); + } + +} diff --git a/lib/plans/all_plans.dart b/lib/plans/all_plans.dart index 6c29216..9be6d2f 100644 --- a/lib/plans/all_plans.dart +++ b/lib/plans/all_plans.dart @@ -138,7 +138,7 @@ class _AllPlansState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => const PlanRequestPage()), + builder: (context) => const PlanRequestsPage()), ); }, child: Text( diff --git a/lib/plans/edit_plan_requests.dart b/lib/plans/edit_plan_requests.dart index a52fcf5..97fe605 100644 --- a/lib/plans/edit_plan_requests.dart +++ b/lib/plans/edit_plan_requests.dart @@ -1,66 +1,86 @@ import 'package:flutter/material.dart'; +import 'package:supplier_new/common/settings.dart'; class EditPlanRequests extends StatefulWidget { - final dynamic order; // Pass order data here - - const EditPlanRequests({super.key, this.order}); + var order; + String? advance; + var status; + EditPlanRequests({this.order,this.advance,this.status}); @override State createState() => _EditPlanRequestsState(); } class _EditPlanRequestsState extends State { - final TextEditingController tankerPriceController = - TextEditingController(text: "2000"); - final TextEditingController waterTypeController = - TextEditingController(text: "Drinking Water"); - final TextEditingController frequencyController = - TextEditingController(text: "4/week"); - final TextEditingController capacityController = - TextEditingController(text: "10000L"); - final TextEditingController timeController = - TextEditingController(text: "10:00 AM - 5:00 PM"); - final TextEditingController quantityController = - TextEditingController(text: "1/day"); - final TextEditingController advanceController = - TextEditingController(text: "40%"); - final TextEditingController startDateController = - TextEditingController(text: "12 July 2025"); - final TextEditingController endDateController = - TextEditingController(text: "21 July 2025"); + final TextEditingController tankerPriceController = TextEditingController(); + final TextEditingController waterTypeController = TextEditingController(); + final TextEditingController capacityController = TextEditingController(); + final TextEditingController timeController = TextEditingController(); + final TextEditingController quantityController = TextEditingController(); + final TextEditingController advanceController = TextEditingController(); + final TextEditingController dateController = TextEditingController(); @override void initState() { super.initState(); + tankerPriceController.text='${widget.order.quoted_amount}'; + waterTypeController.text='${widget.order.type_of_water}'; + quantityController.text='${widget.order.quantity}'; + capacityController.text='${widget.order.capacity}'; + timeController.text='${widget.order.averageTime}'; + dateController.text='${widget.order.time}'; + advanceController.text='${widget.advance}'; // Update summary in real-time as user types tankerPriceController.addListener(() => setState(() {})); + /*capacityController.addListener(() => setState(() {})); + quantityController.addListener(() => setState(() {})); + dateController.addListener(() => setState(() {})); + timeController.addListener(() => setState(() {})); + waterTypeController.addListener(() => setState(() {}));*/ advanceController.addListener(() => setState(() {})); } @override Widget build(BuildContext context) { int tankerPrice = int.tryParse(tankerPriceController.text) ?? 0; - int totalWeeks = 12; // static for now - int weeklyPrice = tankerPrice * 4; // assuming 4 tankers/week - int totalPrice = weeklyPrice * totalWeeks; - double advancePercent = - double.tryParse(advanceController.text.replaceAll('%', '')) ?? 0; - int advancePayable = (totalPrice * (advancePercent / 100)).round(); + int updatedQuantity=int.tryParse(quantityController.text) ?? 0; + String updatedCapacity=capacityController.text ?? ''; + // Parse booking/advance as a double to accept "150.0" then convert to int (round) + double bookingChargesDouble = double.tryParse(advanceController.text.trim()) ?? 0.0; + int bookingCharges = bookingChargesDouble.round(); // or .toInt() / .floor() as you prefer + + int totalPrice = (tankerPrice * updatedQuantity) + bookingCharges; + int advancePayable = bookingCharges; // + int amountToPayAfterDelivery=totalPrice-bookingCharges; return Scaffold( backgroundColor: Colors.white, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: Colors.white, elevation: 0, - leading: IconButton( - icon: const Icon(Icons.close, color: Colors.black), - onPressed: () => Navigator.pop(context), + scrolledUnderElevation: 0, + title: Text( + 'Edit Plan', + style: fontTextStyle(16, Color(0XFF2A2A2A), FontWeight.w600), ), - title: const Text( - "Edit Order", - style: TextStyle(color: Colors.black, fontWeight: FontWeight.w600), + leading: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Padding( + padding: + const EdgeInsets.fromLTRB(16, 8, 8, 8), // Add padding if needed + child: Image.asset( + 'images/cross.png', // Replace with your image path + fit: BoxFit.contain, + color: Color(0XFF2A2A2A), + height: 24, + width: 24, + + ), + ), ), - centerTitle: true, + iconTheme: IconThemeData(color: Color(0XFF2A2A2A)), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), @@ -68,98 +88,209 @@ class _EditPlanRequestsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ // 🔹 Club Info Card - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(12), - ), - child: Row( - children: [ - Expanded( - child: Column( + Material( + elevation: 4, // shadow depth + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, // Material needs a background color + border: Border.all(color: Color(0XFFC9C2F0),width: 0.5), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Chip( - label: Text("New", - style: TextStyle( - color: Colors.blue, - fontSize: 12, - fontWeight: FontWeight.w600)), - backgroundColor: Color(0xFFEAF3FF), - padding: EdgeInsets.symmetric(horizontal: 4), + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Color(0XFFFFFFFF), + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: widget.status.statusColor, + width: 0.5, + ), + ), + child: Text( + widget.status.status, + style: fontTextStyle(12, widget.status.statusColor, FontWeight.w500) + ), ), - Text("Club Kohinoor", - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.w600)), SizedBox(height: 4), - Text("Banjara Hills, Hyderabad", - style: TextStyle(color: Colors.grey, fontSize: 13)), + Text( + widget.order.building_name, + style: fontTextStyle(20, const Color(0XFF2D2E30), FontWeight.w600), + ), + Text( + widget.order.displayAddress, + style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400), + ), ], ), - ), - const Text("5.5 Km", style: TextStyle(color: Colors.black54)), - ], + const Spacer(), + Text( + widget.order.distanceInKm.toString()+'Km', + style: fontTextStyle(12, const Color(0XFF939495), FontWeight.w400), + ), + ], + ), ), ), - const SizedBox(height: 20), - const Text("ORDER DETAILS", - style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14)), + + SizedBox(height:MediaQuery.of(context).size.height * .06,), + Text("PLAN DETAILS", + style: fontTextStyle(12, const Color(0XFF2D2E30), FontWeight.w600),), const SizedBox(height: 12), // 🔹 Two in a row - _twoFields(tankerPriceController, "Tanker Price", - waterTypeController, "Water Type"), - _twoFields(frequencyController, "Frequency", capacityController, - "Capacity"), - _twoFields(timeController, "Time of Delivery", quantityController, - "Quantity"), - _twoFields(advanceController, "Advance", startDateController, - "Start Date"), - _twoFields(endDateController, "End Date", null, null), + _twoFields(tankerPriceController, "Tanker Price",'images/price.png',false, null, null,null,null), + _detailTwoRow( + "Water Type", + "${widget.order.type_of_water}", + "images/water.png", + "Capacity", + "${widget.order.capacity}", + "images/capacity.png", + + ), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Start Date", + "${widget.order.averageTime}", + "images/time.png", + "End Date", + "${widget.order.averageTime}", + "images/advance.png", + ), + SizedBox( + height: MediaQuery.of(context).size.height * .02, + ), + _detailTwoRow( + "Frequency", + "${widget.order.quantity}", + "images/quantity.png", + "", + "", + ""), + + /*_twoFields(capacityController, "Capacity",'images/capacity.png',true,quantityController, "Frequency",'images/quantity.png',true), + _twoFields(waterTypeController, "Water Type",'images/water.png',true,advanceController, "Booking Charges",'images/advance.png',false), + _twoFields(dateController, "Date",'images/calendar_appbar.png',true,timeController, "Time Of Delivery",'images/time.png',true), +*/ const SizedBox(height: 20), - const Text("UPDATED PAYMENT SUMMARY", - style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14)), + Text("UPDATED PAYMENT SUMMARY", + style: fontTextStyle(12,Color(0XFF2D2E30),FontWeight.w600),), const SizedBox(height: 12), _summaryRow("Tanker Price", "₹ $tankerPrice"), - _summaryRow("Tankers per week", "04"), - _summaryRow("Weekly Price", "₹ $weeklyPrice"), - _summaryRow("Total weeks", "$totalWeeks"), + _summaryRow("Quantity", " $updatedQuantity"), + _summaryRow("Capacity", "$updatedCapacity"), + _summaryRow("Booking Charges", "₹ $bookingCharges"), _summaryRow("Total Price", "₹ $totalPrice"), const Divider(), - _summaryRow("Advance", advanceController.text), - _summaryRow("Advance Payable", "₹ $advancePayable"), + + _summaryRow("Booking Charges Payable", "₹ $advancePayable"), + _summaryRow("Amount to Pay (After Delivery)", "₹ $amountToPayAfterDelivery"), ], ), ), bottomNavigationBar: Container( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( - color: Colors.white, - border: Border(top: BorderSide(color: Colors.grey.shade300)), + color: Color(0XFFFFFFFF), + border: Border(top: BorderSide(color: Color(0XFFF5F6F6))), ), child: Row( children: [ Expanded( child: OutlinedButton( - onPressed: () => Navigator.pop(context), - child: const Text("Cancel"), + style: OutlinedButton.styleFrom( + foregroundColor: Color(0XFF000000), + backgroundColor: Color(0xFFFFFFFF), + side: BorderSide(color: Color(0xFF939495)), + padding: EdgeInsets.symmetric(vertical: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), // <-- radius here + ), + ), + onPressed: () { + Navigator.pop(context); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + + Text( + "Cancel", + style: fontTextStyle(14, const Color(0XFF000000), FontWeight.w400), + ), + ], + ), ), ), const SizedBox(width: 8), Expanded( - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.deepPurple, - foregroundColor: Colors.white, + child: OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: Color(0XFFFFFFFF), + backgroundColor: Color(0xFF8270DB), + side: BorderSide(color: Color(0xFF8270DB)), + padding: EdgeInsets.symmetric(vertical: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), // <-- radius here + ), ), - onPressed: () { - // 🔹 Collect updated values - print("Tanker Price: ${tankerPriceController.text}"); - print("Water Type: ${waterTypeController.text}"); - print("Frequency: ${frequencyController.text}"); - // Save logic here + onPressed: () async{ + /*AppSettings.preLoaderDialog(context); + + bool isOnline = await AppSettings.internetConnectivity(); + + if (isOnline) { + var payload = new Map(); + payload["supplierId"] = AppSettings.supplierId; + payload["amount"] = int.parse(widget.order.quoted_amount); + payload["delivery_charges"] = widget.advance; + payload["action"] = 'accept'; + + bool status = await AppSettings.acceptOrderRequests( + payload, widget.order.dbId); + + try { + if (status) { + Navigator.of(context,rootNavigator: true).pop(); + Navigator.pop(context, true); + Navigator.pop(context, true); + } + else{ + Navigator.of(context,rootNavigator: true).pop(); + AppSettings.longFailedToast("Accept of order request Failed"); + } + } catch (e) { + Navigator.of(context,rootNavigator: true).pop(); + print(e); + } + } + else{ + Navigator.of(context,rootNavigator: true).pop(); + AppSettings.longFailedToast("Please Check internet"); + }*/ }, - child: const Text("Send ➤"), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + + Text( + "Send", + style: fontTextStyle(14, const Color(0XFFFFFFFF), FontWeight.w400), + ), + SizedBox(width: 12), + Image.asset('images/send.png', height: 20, width: 20), + + ], + ), ), ), ], @@ -169,36 +300,133 @@ class _EditPlanRequestsState extends State { } /// 🔹 Two fields side by side - Widget _twoFields(TextEditingController? controller1, String? label1, - TextEditingController? controller2, String? label2) { + Widget _twoFields(TextEditingController? controller1, String? label1,String? path1, bool? readOnly1, + TextEditingController? controller2, String? label2,String? path2,bool? readOnly2) { return Row( children: [ Expanded( - child: _textField(controller1, label1), + child: _textField(controller1, label1,path1,readOnly1!), ), const SizedBox(width: 10), if (controller2 != null) Expanded( - child: _textField(controller2, label2), + child: _textField(controller2, label2,path2,readOnly2!), ), ], ); } + Widget _detailTwoRow( + String title1, + String value1, + String path1, + String title2, + String value2, + String path2, { + EdgeInsetsGeometry padding = const EdgeInsets.symmetric(vertical: 6), + }) { + final titleStyle = fontTextStyle(12, Color(0XFF646566), FontWeight.w400); + final valueStyle = fontTextStyle(12, Color(0XFF343637), FontWeight.w500); + + Widget _col(String t, String v, String path) { + return Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (path.isNotEmpty) + Image.asset( + path, + fit: BoxFit.contain, + height: 20, + width: 20, + color: const Color(0XFFC3C4C4), + ), + if (path.isNotEmpty) const SizedBox(width: 6), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t, + style: titleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis), + Text(v, + style: valueStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis), + ], + ), + ) + ], + ), + ), + ); + } + + return Padding( + padding: padding, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _col(title1, value1, path1), + _col(title2, value2, path2), + ], + ), + ); + } + /// 🔹 Custom text field - Widget _textField(TextEditingController? controller, String? label) { + Widget _textField(TextEditingController? controller, String? label,String? path,bool readOnly) { return Container( margin: const EdgeInsets.only(bottom: 12), child: TextField( controller: controller, + cursorColor: primaryColor, + readOnly: readOnly, decoration: InputDecoration( - labelText: label, + counterText: '', + filled: false, + fillColor: Colors.white, + prefixIcon: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0), + child: SizedBox( + width: 18, + height: 18, + child: Image.asset( + path!, + fit: BoxFit.contain, + color: Color(0XFFC3C4C4), + ), + ), + ), + prefixIconConstraints: BoxConstraints( + minWidth: 24, + minHeight: 24, + ), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(4.0), + borderSide: BorderSide(color: Color(0XFFC3C4C4), + width: 1,)), + focusedBorder: !readOnly?OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0), + borderSide: BorderSide(color: Color(0XFF8270DB),width: 1,), + ):OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0), + borderSide: BorderSide(color: Color(0XFFC3C4C4),width: 1,), ), - contentPadding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0), + borderSide: BorderSide(color: Color(0XFFC3C4C4)), + ), + labelText: label, + labelStyle:fontTextStyle(12,Color(0XFF646566),FontWeight.w400), + /* TextStyle(color: greyColor, fontWeight: FontWeight.bold //<-- SEE HERE + ),*/ ), + style:fontTextStyle(12,Color(0XFF343637),FontWeight.w500), ), ); } @@ -210,12 +438,12 @@ class _EditPlanRequestsState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(title, style: const TextStyle(color: Colors.black54)), + Text(title, style: fontTextStyle(12,Color(0XFF646566),FontWeight.w400),), Text(value, - style: const TextStyle( - fontWeight: FontWeight.w600, color: Colors.black)), + style: fontTextStyle(12,Color(0XFF2D2E30),FontWeight.w500),), ], ), ); } + } diff --git a/lib/plans/plan_requests.dart b/lib/plans/plan_requests.dart index 91745a1..f11f68a 100644 --- a/lib/plans/plan_requests.dart +++ b/lib/plans/plan_requests.dart @@ -1,162 +1,192 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:supplier_new/common/settings.dart'; +import 'package:supplier_new/orders/order_requests_model.dart'; +import 'package:supplier_new/plans/accept_plan_requests.dart'; +import 'package:supplier_new/plans/plan_requests_model.dart'; -class PlanRequestPage extends StatefulWidget { - const PlanRequestPage({super.key}); +class PlanRequestsPage extends StatefulWidget { + const PlanRequestsPage({super.key}); @override - State createState() => _PlanRequestPageState(); + State createState() => _PlanRequestsPageState(); } -class _PlanRequestPageState extends State { +class _PlanRequestsPageState extends State { final TextEditingController searchController = TextEditingController(); + List planRequestsList = []; + List filteredList = []; + bool isLoading = false; + + @override + void initState() { + super.initState(); + _fetchPlanRequests(); + // 🔹 Add listener for search + searchController.addListener(() { + _filterOrders(searchController.text); + }); + + } + + void _filterOrders(String query) { + final lowerQuery = query.toLowerCase(); + + setState(() { + if (lowerQuery.isEmpty) { + filteredList = planRequestsList; + } else { + filteredList = planRequestsList.where((order) { + final title = order.building_name.toLowerCase() ?? ''; + final address = order.displayAddress.toLowerCase() ?? ''; + final waterType = order.type_of_water.toLowerCase() ?? ''; + final capacity = order.capacity.toLowerCase() ?? ''; + + return title.contains(lowerQuery) || + address.contains(lowerQuery) || + waterType.contains(lowerQuery) || + capacity.contains(lowerQuery); + }).toList(); + } + }); + } + + Future _fetchPlanRequests() async { + setState(() => isLoading = true); + + try { + final response = await AppSettings.getPlanRequestsFromUsers(); + final data = (jsonDecode(response)['data'] as List) + .map((e) => PlanRequestsModel.fromJson(e)) + .toList(); + if (!mounted) return; + setState(() { + planRequestsList = data; + filteredList = data; + isLoading = false; + }); + } catch (e) { + debugPrint("⚠️ Error fetching orders: $e"); + setState(() => isLoading = false); + } + } + + /// 🔹 Helper to get status based on order time + Map getOrderStatus(String orderTimeStr, String dbStatus) { + String status = "Invalid time"; + Color color = Colors.grey; + + final dbLower = (dbStatus ?? "").toLowerCase().trim(); + + // ✅ Statuses that ignore time + if (dbLower == "reject") return {"status": "Rejected", "color": const Color(0XFFE2483D)}; + if (dbLower == "accept") return {"status": "Accepted", "color": const Color(0XFF0A9E04)}; + if (dbLower == "advance_paid") return {"status": "Accepted", "color": const Color(0XFF0A9E04)}; + if (dbLower == "cancelled" || dbLower == "cancelled_by_user" || dbLower == "cancelled_by_supplier") { + return {"status": "Cancelled", "color": const Color(0XFF757575)}; + } + + // ✅ Time-based status (only if not rejected/accepted/etc.) + if (orderTimeStr.isEmpty) { + // fallback for pending without time + return {"status": "Pending", "color": const Color(0XFFE56910)}; + } + + try { + final format = DateFormat("dd-MM-yyyy HH:mm"); + final orderTime = format.parse(orderTimeStr); + final now = DateTime.now(); + final difference = now.difference(orderTime); + + if (difference.inDays < 2) { + status = "New"; + color = const Color(0XFF1D7AFC); // Blue + } else if (difference.inDays < 10) { + final remaining = 20 - difference.inDays; + status = "Expires in ${remaining} D"; // show time for pending + color = difference.inDays < 10 + ? const Color(0XFFE56910) + : const Color(0XFFE2483D); + } else { + // Expired overrides pending + status = "Expired"; + color = const Color(0XFF757575); + } + } catch (e) { + debugPrint("⚠️ Error parsing time: $e"); + status = "Invalid time"; + color = Colors.grey; + } + + return {"status": status, "color": color}; + } - // ✅ Sample orders list - final List allPlans = [ - PlanModel( - status: "New", - title: "Green Valley Apartments", - location: "Gachibowli", - description: "10,000 L - Drinking water", - price: "₹3,400", - ), - PlanModel( - status: "Expires in 15m", - title: "Lakeview Towers", - location: "Madhapur", - description: "8,000 L - Borewell water", - price: "₹2,700", - ), - PlanModel( - status: "Expired", - title: "Sunrise Residency", - location: "Kukatpally", - description: "12,000 L - Tanker water", - price: "₹4,000", - ), - PlanModel( - status: "Rejected", - title: "Skyline Apartments", - location: "Kompally", - description: "5,000 L - Drinking water", - price: "₹1,600", - ), - PlanModel( - status: "Pending", - title: "Skyline Apartments", - location: "Kompally", - description: "5,000 L - Drinking water", - price: "₹1,600", - ), - PlanModel( - status: "Rejected", - title: "Elite Towers", - location: "Hitech City", - description: "20,000 L - Borewell water", - price: "₹6,000", - ), - ]; @override Widget build(BuildContext context) { - // ✅ Split orders into rejected and others - final rejectedPlans = - allPlans.where((o) => o.status.toLowerCase() == "rejected").toList(); - final otherPlans = - allPlans.where((o) => o.status.toLowerCase() != "rejected").toList(); - return Scaffold( backgroundColor: Colors.white, - appBar: AppBar( - backgroundColor: Colors.white, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.black), - onPressed: () => Navigator.pop(context), - ), - title: const Text( - "Plan Requests", - style: TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - centerTitle: false, - actions: [ - IconButton( - icon: const Icon(Icons.help_outline, color: Colors.black), - onPressed: () {}, - ), - const SizedBox(width: 8), - ], - ), + appBar: AppSettings.SupplierAppBarWithHelpAction('Plan Requests', context), body: Padding( - padding: const EdgeInsets.all(12.0), + padding: const EdgeInsets.all(16.0), child: Column( children: [ - /// Search bar - Row( - children: [ - Expanded( - child: Container( - height: 42, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - border: Border.all(color: Colors.grey.shade300), - color: Colors.white, - ), - child: TextField( - controller: searchController, - decoration: const InputDecoration( - hintText: "Search", - prefixIcon: Icon(Icons.search, color: Colors.grey), - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 10), - ), - ), - ), - ), - const SizedBox(width: 10), - Container( - height: 42, - width: 42, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey.shade300), - color: Colors.white, - ), - child: IconButton( - icon: const Icon(Icons.sort, size: 20, color: Colors.black), - onPressed: () {}, - ), - ), - ], - ), + /// 🔹 Search Bar + _buildSearchBar(), const SizedBox(height: 16), - /// Orders List + /// 🔹 Orders List Expanded( - child: ListView( - children: [ - // Active / Other Orders - ...otherPlans.map((o) => OrderCard(order: o)), - - // Rejected Orders Section - if (rejectedPlans.isNotEmpty) ...[ - const SizedBox(height: 12), - const Text( - "Rejected Requests", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - ), - const SizedBox(height: 8), - ...rejectedPlans.map((o) => OrderCard(order: o)), - ], - ], + child: isLoading + ? const Center(child: CircularProgressIndicator()) + : filteredList .isEmpty?Center( + child: Text( + 'No Data Available', + style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700), + ), + ):ListView.builder( + itemCount: filteredList .length, + itemBuilder: (context, index) { + final order = filteredList [index]; + final statusMap = getOrderStatus(order.time ?? "", order.status ?? ""); + final status = statusMap['status']; + final color = statusMap['color']; + + final cardModel = OrderCardModel( + status: status, + statusColor: color, + title: order.building_name ?? "", + location: order.displayAddress ?? "", + description: "${order.capacity ?? ''} - ${order.type_of_water ?? ''}", + price: "₹${AppSettings.formDouble(order.quoted_amount) ?? ''}", + ); + + final noNavigateStatuses = ["expired", "rejected", "accepted", "advance_paid", "cancelled"]; + final disableNavigation = noNavigateStatuses.contains(status.toLowerCase()); + + final card = OrderCard(order: cardModel); + + return disableNavigation + ? card // just show the card, no tap + : GestureDetector( + onTap: () async { + final result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AcceptPlanRequests(order: order, status: cardModel), + ), + ); + + // If result indicates API reload + if (result == true) { + _fetchPlanRequests(); + } + }, + child: card, + ); + }, ), ), ], @@ -164,18 +194,98 @@ class _PlanRequestPageState extends State { ), ); } + + Widget _buildSearchBar() { + return Row( + children: [ + Expanded( + child: Container( + height: 42, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(22), + border: Border.all(color: const Color(0XFF939495)), + ), + child: TextField( + controller: searchController, + decoration: InputDecoration( + hintText: "Search", + hintStyle: fontTextStyle(16, const Color(0XFF646566), FontWeight.w400), + prefixIcon: SizedBox( + height: 20, + width: 20, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.asset( + 'images/search.png', + fit: BoxFit.contain, + ), + ), + ), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(vertical: 10), + ), + ), + ), + ), + const SizedBox(width: 10), + Container( + height: 42, + width: 42, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + color: Color(0XFFF5F6F6), + ), + child: IconButton( + icon: Image.asset( + 'images/filter.png', // your image path + height: 20, + width: 20, + fit: BoxFit.contain, + ), + onPressed: () { + // Your onPressed action + }, + ) + ), + const SizedBox(width: 5), + Container( + height: 42, + width: 42, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + color: Color(0XFFF5F6F6), + ), + child: IconButton( + icon: Image.asset( + 'images/sort.png', // your image path + height: 20, + width: 20, + fit: BoxFit.contain, + ), + onPressed: () { + // Your onPressed action + }, + ) + ), + ], + ); + } } -/// Order Model -class PlanModel { +/// 🔹 UI Model for rendering OrderCard +class OrderCardModel { final String status; + final Color statusColor; final String title; final String location; final String description; final String price; - PlanModel({ + OrderCardModel({ required this.status, + required this.statusColor, required this.title, required this.location, required this.description, @@ -183,109 +293,73 @@ class PlanModel { }); } -/// Order Card widget +/// 🔹 Card widget class OrderCard extends StatelessWidget { - final PlanModel order; + final OrderCardModel order; const OrderCard({super.key, required this.order}); - Color _getStatusColor() { - switch (order.status.toLowerCase()) { - case "new": - return Colors.blue; - case "expires in 15m": - case "expires in 5m": - return Colors.orange; - case "expired": - return Colors.grey; - case "rejected": - return Colors.red; - default: - return Colors.black; - } - } - @override Widget build(BuildContext context) { - final statusColor = _getStatusColor(); - return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white, + color: Color(0XFFF6F6F6), borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey.shade300), ), child: Row( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - /// Left content + /// 🔹 Left content Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Status chip + /// Status chip Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: statusColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), + color: Color(0XFFF6F6F6), + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: order.statusColor, + width: 0.5, + ), ), child: Text( - order.status, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w600, - color: statusColor, - ), + order.status, + style: fontTextStyle(12, order.statusColor, FontWeight.w500) ), ), - const SizedBox(height: 6), + SizedBox(height:MediaQuery.of(context).size.height * .008,), - // Title + /// Title Text( - order.title, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - ), + order.title, + style: fontTextStyle(16, Color(0XFF2D2E30), FontWeight.w600) ), - // Location + /// Location Text( - order.location, - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, - ), + order.location, + style: fontTextStyle(10, Color(0XFF939495), FontWeight.w400) ), - const SizedBox(height: 4), - // Description + /// Description Text( - order.description, - style: const TextStyle( - fontSize: 12, - color: Colors.blue, - fontWeight: FontWeight.w500, - ), + order.description, + style: fontTextStyle(14, Color(0XFF8270DB), FontWeight.w500) ), ], ), ), - /// Price + /// 🔹 Price Text( - order.price, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - ), + order.price, + style: fontTextStyle(16, Color(0XFF444444), FontWeight.w600) ), ], ), diff --git a/lib/plans/plan_requests_model.dart b/lib/plans/plan_requests_model.dart new file mode 100644 index 0000000..692b95e --- /dev/null +++ b/lib/plans/plan_requests_model.dart @@ -0,0 +1,87 @@ + +import 'package:supplier_new/common/settings.dart'; +import 'package:geolocator/geolocator.dart'; + +class PlanRequestsModel { + String building_name = ''; + String address = ''; + String type_of_water = ''; + String capacity = ''; + String quantity = ''; + String time = ''; + String averageTime = ''; + String quoted_amount = ''; + String displayAddress=''; + double lat=0; + double lng=0; + double distanceInMeters=0; + double distanceInKm=0.0; + String dbId = ''; + String status=''; + String frequency=''; + + PlanRequestsModel(); + + factory PlanRequestsModel.fromJson(Map json){ + PlanRequestsModel rtvm = new PlanRequestsModel(); + + // rtvm.building_name = json['customer_details']['buildingName'] ?? ''; + rtvm.dbId = json['_id']?? ''; + // rtvm.address = json['customer_details']['profile']['address1'] ?? ''; + rtvm.type_of_water = json['type_of_water'] ?? ''; + rtvm.capacity = json['capacity'] ?? ''; + //rtvm.quantity = json['quantity']?? ''; + rtvm.frequency = json['frequency']?? ''; + + if(rtvm.frequency.toString().toLowerCase()=='weekly_twice'){ + rtvm.quantity="2/Week"; + } + if(rtvm.frequency.toString().toLowerCase()=='weekly_thrice'){ + rtvm.quantity="2/Week"; + } + else if(rtvm.frequency.toString().toLowerCase()=='weekly_thrice'){ + rtvm.quantity="3/Week"; + } + else if(rtvm.frequency.toString().toLowerCase()=='weekly_once'){ + rtvm.quantity="1/Week"; + } + else if(rtvm.frequency.toString().toLowerCase()=='daily'){ + rtvm.quantity="7/Week"; + } + + + rtvm.averageTime = json['time'] ?? ''; + rtvm.time = json['my_supplier_entry']['time'] ?? ''; + rtvm.status = json['my_supplier_entry']['status'] ?? ''; + //rtvm.quoted_amount = json['my_supplier_entry']['quoted_amount'].toString() ?? ''; + //rtvm.lng=json['customer_details']['longitude'] ?? 0.0; + //rtvm.lat=json['customer_details']['latitude'] ?? 0.0; + + // Split and trim + List parts = rtvm.address.split(',').map((e) => e.trim()).toList(); + +// Usually, the locality is the part before the main city (Hyderabad)displayAddress = ""; + if (parts.length >= 2) { + rtvm.displayAddress = parts[parts.length -4]; // "Banjara Hills" + } + // Distance in meters + rtvm.distanceInMeters = double.parse( + Geolocator.distanceBetween( + rtvm.lat, + rtvm.lng, + AppSettings.supplierLatitude, + AppSettings.supplierLongitude, + ).toStringAsFixed(2), + ); + +// Distance in km + rtvm.distanceInKm = double.parse( + (rtvm.distanceInMeters / 1000).toStringAsFixed(2), + ); + + return rtvm; + } + Map toJson() => { + "boreName":this.building_name, + }; +} \ No newline at end of file