import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; import 'package:razorpay_flutter/razorpay_flutter.dart'; import '../../Notifiers/HelpAndComplaintProvider.dart'; import '../../Notifiers/PayAmountProvider.dart'; import '../../Notifiers/serviceAndJobCardListProvier.dart'; import '../../Utility/AppColors.dart'; import '../../Utility/CustomSnackbar.dart'; import '../../Utility/SharedpreferencesService.dart'; class Jobcardlistscreen extends StatefulWidget { final accId; final sessionId; final complaintId; const Jobcardlistscreen({ super.key, required this.accId, required this.sessionId, required this.complaintId, }); @override State createState() => _JobcardlistscreenState(); } class _JobcardlistscreenState extends State { late Razorpay _razorpay; bool? isSuccess; var paymentMethod = ""; var User_contact = "0"; bool bottomSheetButtonClicked = false; final prefs = SharedPreferencesService.instance; @override void initState() { // TODO: implement initState super.initState(); _razorpay = Razorpay(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { final provider = Provider.of( context, listen: false, ); provider.fetchAllJobCardsListAPI( widget.accId, widget.sessionId, widget.complaintId, ); }); } //_________________________________________________________ void _handlePaymentSuccess(PaymentSuccessResponse response) { setState(() async { final provider = Provider.of(context, listen: false); await provider.getPaymentStatus( sessionId: widget.sessionId, empId: widget.accId, razorpayOrderId: response.orderId.toString(), ); final data = provider.statusResponse; // Navigator.push( // context, // MaterialPageRoute(builder: (context) => PaymentSuccessFaillScreen( // total: "${data?.amount}", // date: "${data?.date}", // payMode: "UPI", // status: "Success", // )), // ); _razorpay.clear(); CustomSnackBar.showSuccess( context: context, message: data?.message ?? "Payment Success!", ); // buttonLoading = false; }); } void _handlePaymentError(PaymentFailureResponse response) { setState(() async { CustomSnackBar.showError( context: context, message: "Payment failed, please try again.", ); }); _razorpay.clear(); CustomSnackBar.showError( context: context, message: "Payment failed, please try again.", ); } void _handleExternalWallet(ExternalWalletResponse response) { _razorpay.clear(); } Future payAmountFunction(String amount) async { try { final provider = Provider.of(context, listen: false); await provider.payAmount( sessionId: widget.sessionId, empId: widget.accId, amount: amount, refType: "Gen Service", refId: "1", ); final data = provider.payResponse; if (data != null) { if (data.error == "0") { openCheckout(data.orderId, data.razorKey!); } else { CustomSnackBar.showError( context: context, message: "${data.message}", ); debugPrint("❌ Could not Complete Payment: ${data.message}"); } } else { debugPrint("❌ No response received from PayAmount API"); } } catch (e) { debugPrint("❌ 'Error occurred: $e'"); } } //razorpay payments__________________________________________________________ void openCheckout(razorPayOrderId, String razorpayKey) async { final String? mobNumber = await prefs.getString("mob_number"); _razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess); _razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError); _razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet); // _buildCheckWidget(); Map options = { 'key': razorpayKey, 'amount': int.parse("${((0) * 100).round()}"), 'name': 'Gen Service', 'order_id': razorPayOrderId, 'description': "Bill", 'currency': 'INR', 'method': 'upi', 'prefill': {'contact': mobNumber, 'email': ''}, }; // print(options); try { _razorpay.open(options); } catch (e, s) { // FirebaseCrashlytics.instance.log('Error occurred: $e'); // FirebaseCrashlytics.instance.recordError(e, s); debugPrint(e.toString()); } } void verifyPayment(String orderId) { isSuccess = true; setState(() { // toast(context, "Order Placed Successfully"); // print("Verify Payment"); }); _razorpay.clear(); } // void onError(CFErrorResponse errorResponse, String orderId) { // isSuccess = false; // setState(() { // // print(errorResponse.getMessage()); // // print("Error while making payment"); // }); // } @override Widget build(BuildContext context) { return Consumer2( builder: (context, provider, hcProvider, child) { final isLoading = provider.isLoading; final error = provider.errorMessage; final response = provider.allJobCardResponse; final data = response?.jobCardList ?? []; if (isLoading) { return const Scaffold( backgroundColor: AppColors.backgroundRegular, body: Center( child: CircularProgressIndicator(color: AppColors.buttonColor), ), ); } if (error != null) { return Scaffold( backgroundColor: AppColors.backgroundRegular, body: Center( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Error Icon Container( width: 120, height: 120, decoration: BoxDecoration( color: Colors.red.withOpacity(0.1), shape: BoxShape.circle, ), child: const Icon( Icons.error_outline_rounded, size: 60, color: Colors.red, ), ), const SizedBox(height: 24), // Error Title const Text( "Oops! Something went wrong", style: TextStyle( fontSize: 20, fontWeight: FontWeight.w600, color: Colors.black87, fontFamily: "Poppins", ), ), const SizedBox(height: 12), // Error Message Text( error, textAlign: TextAlign.center, style: const TextStyle( fontSize: 14, color: Colors.grey, fontFamily: "Poppins", height: 1.4, ), ), const SizedBox(height: 32), // Retry Button ElevatedButton.icon( onPressed: () async { // Show loading state setState(() {}); await Future.delayed(const Duration(milliseconds: 300)); // Retry fetching data final provider = Provider.of( context, listen: false, ); await provider.fetchAllJobCardsListAPI( widget.accId, widget.sessionId, widget.complaintId, ); }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.buttonColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), ), elevation: 2, ), icon: const Icon(Icons.refresh_rounded, size: 20), label: const Text( "Try Again", style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, fontFamily: "Poppins", ), ), ), const SizedBox(height: 16), // Alternative Action TextButton( onPressed: () { // Go back or navigate to home Navigator.maybePop(context); }, child: const Text( "Go Back", style: TextStyle( fontSize: 14, color: Colors.grey, fontFamily: "Poppins", ), ), ), ], ), ), ), ); } if (data == null) { return const Scaffold( backgroundColor: AppColors.backgroundRegular, body: Center(child: Text("No data found.")), ); } return RefreshIndicator.adaptive( color: AppColors.amountText, onRefresh: () async { await Future.delayed(const Duration(milliseconds: 600)); }, child: Scaffold( backgroundColor: AppColors.backgroundRegular, body: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ SliverAppBar( stretch: true, pinned: true, expandedHeight: 75, backgroundColor: AppColors.backgroundRegular, elevation: 0, // Remove shadow automaticallyImplyLeading: false, toolbarHeight: 0, // Remove toolbar space collapsedHeight: 0, // Completely collapse to 0 height flexibleSpace: FlexibleSpaceBar( stretchModes: const [StretchMode.fadeTitle], background: Container( decoration: BoxDecoration( gradient: AppColors.balanceBarGradientA, ), child: SafeArea( child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 20, ), child: SizedBox( child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ InkResponse( onTap: () { HapticFeedback.selectionClick(); Navigator.pop(context, true); }, child: SvgPicture.asset( "assets/svg/appbar_back.svg", height: 25, ), ), SizedBox(width: 10), Expanded( flex: 4, child: InkResponse( onTap: () { HapticFeedback.selectionClick(); Navigator.pop(context, true); }, child: Text( "Job Cards List", overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( fontSize: 16, color: Colors.white, height: 1.1, ), ), ), ), ], ), ), ), ), ), ), ), SliverToBoxAdapter( child: Container( color: Color(0xFF4076FF), child: Container( decoration: const BoxDecoration( color: AppColors.backgroundRegular, borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 4), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: data!.length, itemBuilder: (context, j) { return Container( margin: const EdgeInsets.all(5), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(16), border: Border.all( width: 1.1, color: AppColors.buttonColor, ), ), child: Column( children: [ Row( children: [ Expanded( flex: 5, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( data[j].date!, maxLines: 2, style: const TextStyle( color: AppColors.subtitleText, fontSize: 12, ), ), SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "Job Card", style: TextStyle( fontSize: 14, color: AppColors .nearDarkText, ), ), InkResponse( onTap: () { hcProvider .fetchJobCardProductDetails( widget.accId, widget.sessionId, data[j]!.id, ); Future.delayed( Duration( milliseconds: 400, ), () { _showJobCardProductSheet( context, data[j]!.date, data[j]!.totalPrice ); }, ); }, child: Text( " ⓘ View Details", style: TextStyle( fontSize: 14, color: AppColors .buttonColor, ), ), ), ], ), ], ), ), Expanded( flex: 2, child: Text( "₹${data[j].totalPrice}", textAlign: TextAlign.right, style: TextStyle( fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.nearDarkText, ), ), ), ], ), Divider( thickness: 0.3, color: AppColors.subtitleText, ), InkResponse( onTap: () => _openPaymentSheet(context, data[j].totalPrice.toString()), child: Container( padding: EdgeInsets.symmetric( vertical: 16, horizontal: 10, ), decoration: BoxDecoration( color: AppColors.buttonColor, borderRadius: BorderRadius.circular( 25, ), ), child: Center( child: Text( "Pay Now", style: TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600, ), ), ), ), ), ], ), ); }, ), ], ), ), ), ), ], ), ), ); }, ); } Future _showJobCardProductSheet(context,date,amount) { return showModalBottomSheet( useSafeArea: true, isDismissible: true, isScrollControlled: true, showDragHandle: true, backgroundColor: Colors.white, enableDrag: true, context: context, builder: (context) { return StatefulBuilder( builder: (context, setState) { return SafeArea( child: Consumer( builder: (context, provider, child) { final jobcardProductData = provider.jobCardResponse; final list = jobcardProductData?.jobCardProducts; final data = provider.compDetailsResponse; final jobCardsData = data!.jobCardList?.firstOrNull; return Container( margin: EdgeInsets.only( bottom: 15, left: 15, right: 15, top: 10, ), padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${date}", style: TextStyle( color: AppColors.subtitleText, fontSize: 12, ), ), Text( "Job Card Details", style: TextStyle( color: AppColors.nearDarkText, fontSize: 14, ), ), ], ), ), SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( "Total Amount", style: TextStyle( color: AppColors.subtitleText, fontSize: 12, ), ), Text( "₹${amount}", style: TextStyle( color: AppColors.buttonColor, fontSize: 14, ), ), ], ), ), ], ), Divider( thickness: 0.3, color: AppColors.subtitleText, ), ListView.builder( shrinkWrap: true, itemCount: list?.length, physics: NeverScrollableScrollPhysics(), itemBuilder: (BuildContext context, int j) { return Padding( padding: EdgeInsets.symmetric(vertical: 5), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "${j + 1}. ${list?[j].partName}", style: TextStyle( fontSize: 14, color: AppColors.nearDarkText, ), ), Text( "₹${list?[j].price} * ${list?[j].qty}", style: TextStyle( fontSize: 14, color: AppColors.nearDarkText, ), ), Text( "₹${list?[j].totalPrice}", style: TextStyle( fontSize: 14, color: AppColors.nearDarkText, ), ), ], ), ); }, ), ], ), ), ); }, ), ); }, ); }, ).whenComplete(() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { setState(() {}); }); }); } void _openPaymentSheet(BuildContext context, String totalAmountStr) { TextEditingController amountController = TextEditingController(); bool isPartPayment = false; final double totalAmount = double.tryParse(totalAmountStr) ?? 0; showModalBottomSheet( isScrollControlled: true, backgroundColor: Colors.white, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), context: context, builder: (context) { return StatefulBuilder( builder: (context, setState) { return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, top: 16, left: 16, right: 16, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Handle Bar Center( child: Container( width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(4), ), ), ), const SizedBox(height: 16), // Title const Text( "Balance Amount Bill", style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, fontFamily: "Poppins", ), ), const SizedBox(height: 6), Divider(), const SizedBox(height: 10), // Pay Total Option GestureDetector( onTap: () { setState(() => isPartPayment = false); }, child: Row( children: [ Radio( value: false, groupValue: isPartPayment, onChanged: (v) => setState(() => isPartPayment = v!), activeColor: const Color(0xFF008CDE), ), // Radio( // value: false, // groupValue: isPartPayment, // onChanged: (v) => setState(() => isPartPayment = v!), // ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Pay Total", style: TextStyle( fontSize: 14, fontFamily: "Poppins", fontWeight: FontWeight.w400, ), ), Text( "Avoid late payment fees.", style: TextStyle( color: Color(0xff5FBB54), fontSize: 12, fontFamily: "Poppins", ), ), ], ), const Spacer(), Text( "₹${totalAmount.toStringAsFixed(0)}", style: const TextStyle( fontSize: 14, fontFamily: "Poppins", fontWeight: FontWeight.w400, ), ), ], ), ), const SizedBox(height: 10), // Part Payment Option GestureDetector( onTap: () { setState(() => isPartPayment = true); }, child: Row( children: [ Radio( value: true, groupValue: isPartPayment, onChanged: (v) => setState(() => isPartPayment = v!), activeColor: const Color(0xFF008CDE), ), const Text( "Part Payment", style: TextStyle( fontSize: 14, fontFamily: "Poppins", fontWeight: FontWeight.w400, ), ), const SizedBox(width: 24), Expanded( child: Container( height: 50, alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(12), ), child: TextFormField( controller: amountController, enabled: isPartPayment, style: const TextStyle( fontSize: 14, fontFamily: "Poppins", color: Colors.black87, ), keyboardType: TextInputType.number, decoration: const InputDecoration( hintText: "Enter amount", hintStyle: TextStyle( fontSize: 14, fontFamily: "Poppins", color: Colors.grey, ), border: InputBorder.none, ), ), ), ), ], ), ), const SizedBox(height: 6), Divider(), const SizedBox(height: 6), // Continue Button SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { double enteredAmount = isPartPayment ? double.tryParse(amountController.text) ?? 0 : totalAmount; if (enteredAmount <= 0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Please enter a valid amount"), ), ); return; } if (isPartPayment && enteredAmount > totalAmount) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( "Entered amount cannot exceed total amount", ), ), ); return; } Navigator.pop(context); // Pass selected amount to your payAmountFunction payAmountFunction(enteredAmount.toStringAsFixed(2)); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF008CDE), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), padding: const EdgeInsets.symmetric(vertical: 16), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 22), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Continue Payment", style: TextStyle( fontFamily: "Poppins", color: Colors.white, fontSize: 16, ), ), SvgPicture.asset( "assets/svg/continue_ic.svg", color: Colors.white, height: 25, width: 25, ), ], ), ), ), ), const SizedBox(height: 16), ], ), ); }, ); }, ); } }