// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:enaklo_pos/core/extensions/string_ext.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart'; import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/get_table_status/get_table_status_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/order/order_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/payment_methods/payment_methods_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/status_table/status_table_bloc.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; import 'package:enaklo_pos/presentation/home/models/order_type.dart'; import 'package:enaklo_pos/presentation/home/widgets/order_menu.dart'; import 'package:enaklo_pos/presentation/home/widgets/success_payment_dialog.dart'; import 'package:enaklo_pos/presentation/home/widgets/order_type_selector.dart'; import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart'; import 'package:enaklo_pos/data/models/response/payment_methods_response_model.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import '../../../core/components/buttons.dart'; import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; class PaymentTablePage extends StatefulWidget { final DraftOrderModel? draftOrder; final TableModel? table; const PaymentTablePage({ super.key, this.draftOrder, this.table, }); @override State createState() => _PaymentTablePageState(); } class _PaymentTablePageState extends State { final totalPriceController = TextEditingController(); final customerController = TextEditingController(); PaymentMethod? selectedPaymentMethod; int totalPriceFinal = 0; int discountAmountFinal = 0; // Helper method to handle post-payment cleanup Future _handlePostPaymentCleanup() async { if (widget.table != null && widget.draftOrder?.id != null) { // Update table status to available // final newTable = TableModel( // id: widget.table!.id, // tableName: widget.table!.tableName, // status: 'available', // orderId: 0, // paymentAmount: 0, // startTime: DateTime.now().toIso8601String(), // position: widget.table!.position, // ); // Update table status // await ProductLocalDatasource.instance.updateStatusTable(newTable); // Remove draft order await ProductLocalDatasource.instance .removeDraftOrderById(widget.draftOrder!.id!); // Refresh table status context.read().add( GetTableStatusEvent.getTablesStatus('all'), ); log("Table ${widget.table!.tableName} freed up and draft order removed"); } // Safely navigate back - pop multiple times to get to table management // Pop the success dialog first, then the payment page if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); // Pop success dialog if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); // Pop payment page } } } @override void initState() { context .read() .add(GetTableStatusEvent.getTablesStatus('available')); context .read() .add(PaymentMethodsEvent.fetchPaymentMethods()); super.initState(); // Set a default payment method in case API fails selectedPaymentMethod = PaymentMethod( id: "4b1c0d21-c98a-4fc0-a2f9-8d90a0c9d905", organizationId: "3e8b1793-d18b-40c4-a03d-0c6480b630c7", name: "CASH", type: "cash", isActive: true, createdAt: DateTime.tryParse('2025-07-18T03:43:13.857048+07:00'), updatedAt: DateTime.tryParse('2025-07-18T03:43:13.857048+07:00'), ); } @override void dispose() { totalPriceController.dispose(); customerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SafeArea( child: Hero( tag: 'table_payment_screen', child: Scaffold( appBar: AppBar( title: Text('Payment - Table ${widget.table?.tableName}'), backgroundColor: AppColors.primary, foregroundColor: Colors.white, leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { Navigator.of(context).pop(); }, ), actions: [ TextButton( onPressed: () { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Kembali'), content: const Text( 'Apakah Anda yakin ingin kembali? Order akan tetap tersimpan.'), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); // Close dialog }, child: const Text('Tidak'), ), TextButton( onPressed: () { Navigator.of(context).pop(); // Close dialog Navigator.of(context) .pop(); // Go back to previous page }, child: const Text('Ya'), ), ], ); }, ); }, child: const Text( 'Kembali', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold), ), ), ], ), body: Row( children: [ // LEFT CONTENT Expanded( flex: 2, child: Align( alignment: Alignment.topCenter, child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Konfirmasi', style: TextStyle( color: AppColors.primary, fontSize: 20, fontWeight: FontWeight.w600, ), ), Text( 'Orders Table ${widget.table?.tableName}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ], ), const SpaceHeight(8.0), const Divider(), const SpaceHeight(24.0), const Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Item', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w600, ), ), SizedBox( width: 160, ), SizedBox( width: 50.0, child: Text( 'Qty', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), SizedBox( child: Text( 'Price', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ], ), const SpaceHeight(8), const Divider(), const SpaceHeight(8), BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => const Center( child: Text('No Items'), ), loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) { if (products.isEmpty) { return const Center( child: Text('No Items'), ); } return ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => OrderMenu(data: products[index]), separatorBuilder: (context, index) => const SpaceHeight(16.0), itemCount: products.length, ); }, ); }, ), const SpaceHeight(16.0), const SpaceHeight(8.0), const Divider(), const SpaceHeight(8.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Sub total', style: TextStyle(color: AppColors.grey), ), BlocBuilder( builder: (context, state) { final price = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => products.fold( 0, (previousValue, element) => previousValue + (element.product.price! * element.quantity), )); return Text( price.currencyFormatRp, style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, ), ); }, ), ], ), const SpaceHeight(16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Diskon', style: TextStyle(color: AppColors.grey), ), BlocBuilder( builder: (context, state) { final discount = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) { log("discountAmount: $discountAmount"); return discountAmount; }); discountAmountFinal = discount.toInt(); return Text( discount.toInt().currencyFormatRp, style: TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, ), ); }, ), ], ), const SpaceHeight(16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Pajak PB1', style: TextStyle(color: AppColors.grey), ), BlocBuilder( builder: (context, state) { final tax = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => tax, ); final price = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => products.fold( 0, (previousValue, element) => previousValue + (element.product.price! * element.quantity), ), ); final discount = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) { return discountAmount; }); final subTotal = price - discount; final finalTax = subTotal * (tax / 100); return Text( '$tax % (${finalTax.toInt().currencyFormatRp})', style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, ), ); }, ), ], ), const SpaceHeight(16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Biaya Layanan', style: TextStyle(color: AppColors.grey), ), BlocBuilder( builder: (context, state) { state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => tax, ); final price = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => products.fold( 0, (previousValue, element) => previousValue + (element.product.price! * element.quantity), ), ); final discount = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) { return discountAmount; }); final serviceCharge = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => serviceCharge, ); final subTotal = price - discount; final finalServiceCharge = subTotal * (serviceCharge / 100); return Text( '$serviceCharge % (${finalServiceCharge.toInt().currencyFormatRp}) ', style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, ), ); }, ), ], ), const SpaceHeight(16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Total', style: TextStyle( color: AppColors.grey, fontWeight: FontWeight.bold, fontSize: 16), ), BlocBuilder( builder: (context, state) { final price = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => products.fold( 0, (previousValue, element) => previousValue + (element.product.price! * element.quantity), ), ); final discount = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) { return discountAmount; }); final serviceCharge = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => serviceCharge, ); final tax = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => tax, ); final subTotal = price - discount; final finalTax = subTotal * (tax / 100); final service = subTotal * (serviceCharge / 100); final total = subTotal + finalTax + service; totalPriceFinal = total.ceil(); totalPriceController.text = total.ceil().toString(); return Text( total.ceil().currencyFormatRp, style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, fontSize: 16, ), ); }, ), ], ), // const SpaceHeight(20.0), // Button.filled( // onPressed: () {}, // label: 'Lanjutkan Pembayaran', // ), ], ), ), ), ), // RIGHT CONTENT Expanded( flex: 3, child: Align( alignment: Alignment.topCenter, child: Stack( children: [ SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Pembayaran', style: TextStyle( color: AppColors.primary, fontSize: 20, fontWeight: FontWeight.w600, ), ), const SpaceHeight(16.0), const Divider(), const SpaceHeight(8.0), const Text( 'Customer', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w500, ), ), const SpaceHeight(12.0), BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () { return SizedBox.shrink(); }, loaded: (items, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) { customerController.text = draftName; return TextFormField( readOnly: true, controller: customerController, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), ), hintText: 'Nama Customer', ), ); }, ); }, ), const SpaceHeight(8.0), const Divider(), const SpaceHeight(8.0), const OrderTypeSelector(), const SpaceHeight(8.0), const Divider(), const SpaceHeight(8.0), const Text( 'Metode Bayar', style: TextStyle( color: AppColors.primary, fontSize: 20, fontWeight: FontWeight.w600, ), ), const SpaceHeight(12.0), BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => const Center( child: CircularProgressIndicator(), ), loading: () => const Center( child: Column( children: [ CircularProgressIndicator(), SizedBox(height: 8.0), Text('Loading payment methods...'), ], ), ), error: (message) => Column( children: [ Center( child: Text( 'Error loading payment methods: $message'), ), const SpaceHeight(16.0), Button.filled( onPressed: () { context .read() .add(PaymentMethodsEvent .fetchPaymentMethods()); }, label: 'Retry', ), ], ), loaded: (paymentMethods) { log("Loaded ${paymentMethods.length} payment methods"); paymentMethods.forEach((method) { log("Payment method: ${method.name} (ID: ${method.id})"); }); if (paymentMethods.isEmpty) { return Column( children: [ const Center( child: Text( 'No payment methods available'), ), const SpaceHeight(16.0), Button.filled( onPressed: () { context .read() .add(PaymentMethodsEvent .fetchPaymentMethods()); }, label: 'Retry', ), ], ); } // Set default selected payment method if none selected or if current selection is not in the list if (selectedPaymentMethod == null || !paymentMethods.any((method) => method.id == selectedPaymentMethod?.id)) { selectedPaymentMethod = paymentMethods.first; } return Wrap( spacing: 12.0, runSpacing: 8.0, children: paymentMethods.map((method) { final isSelected = selectedPaymentMethod?.id == method.id; return Container( constraints: const BoxConstraints( minWidth: 120.0, ), decoration: isSelected ? BoxDecoration( border: Border.all( color: AppColors.primary, width: 2.0, ), borderRadius: BorderRadius.circular( 8.0), ) : null, child: isSelected ? Button.filled( width: double.infinity, height: 50.0, onPressed: () { setState(() { selectedPaymentMethod = method; }); }, label: method.name ?.isNotEmpty == true ? method.name! : 'Unknown', ) : Button.outlined( width: double.infinity, height: 50.0, onPressed: () { setState(() { selectedPaymentMethod = method; }); }, label: method.name ?.isNotEmpty == true ? method.name! : 'Unknown', ), ); }).toList(), ); }, ); }, ), const SpaceHeight(8.0), const Divider(), const SpaceHeight(8.0), const Text( 'Total Bayar', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w500, ), ), const SpaceHeight(12.0), TextFormField( controller: totalPriceController, keyboardType: TextInputType.number, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), ), hintText: 'Total harga', ), ), const SpaceHeight(45.0), Row( children: [ Button.filled( width: 150.0, onPressed: () {}, label: 'UANG PAS', ), const SpaceWidth(20.0), Button.filled( width: 150.0, onPressed: () {}, label: 'Rp 250.000', ), const SpaceWidth(20.0), Button.filled( width: 150.0, onPressed: () {}, label: 'Rp 300.000', ), ], ), const SpaceHeight(100.0), ]), ), Align( alignment: Alignment.bottomCenter, child: ColoredBox( color: AppColors.white, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 24.0, vertical: 16.0), child: Row( children: [ Flexible( child: Button.outlined( onPressed: () => context.pop(), label: 'Kembali', ), ), const SpaceWidth(8.0), Flexible( child: Button.outlined( onPressed: () { // Show void confirmation dialog showDialog( context: context, builder: (context) => AlertDialog( title: Row( children: [ Icon(Icons.warning, color: AppColors.red), SizedBox(width: 8), Text('Batalkan Pesanan?'), ], ), content: Text( 'Apakah anda yakin ingin membatalkan pesanan untuk meja ${widget.table?.tableName ?? "ini"}?\n\nPesanan akan dihapus secara permanen.'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Tidak', style: TextStyle( color: AppColors.primary)), ), BlocListener( listener: (context, state) { state.maybeWhen( orElse: () {}, success: () { Navigator.pop( context); // Close void dialog Navigator.pop( context); // Close payment page ScaffoldMessenger.of( context) .showSnackBar( const SnackBar( content: Text( 'Pesanan berhasil dibatalkan'), backgroundColor: AppColors.primary, ), ); }, ); }, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppColors.red, ), onPressed: () { // Void the order if (widget.table != null) { // final newTable = TableModel( // id: widget.table!.id, // tableName: widget // .table!.tableName, // status: 'available', // orderId: 0, // paymentAmount: 0, // startTime: DateTime.now() // .toIso8601String(), // position: widget // .table!.position, // ); // context // .read() // .add( // StatusTableEvent // .statusTabel( // newTable), // ); } // Remove draft order from local storage if (widget.draftOrder?.id != null) { ProductLocalDatasource .instance .removeDraftOrderById( widget.draftOrder! .id!); } log("Voided order from payment page"); }, child: const Text( "Ya, Batalkan", style: TextStyle( color: Colors.white), ), ), ), ], ), ); }, label: 'Batalkan', ), ), const SpaceWidth(8.0), BlocListener( listener: (context, state) { // final newTable = TableModel( // id: widget.table!.id, // tableName: widget.table!.tableName, // status: 'available', // orderId: 0, // paymentAmount: 0, // startTime: // DateTime.now().toIso8601String(), // position: widget.table!.position, // ); // context.read().add( // StatusTableEvent.statusTabel( // newTable, // ), // ); ProductLocalDatasource.instance .removeDraftOrderById( widget.draftOrder!.id!); }, child: BlocBuilder( builder: (context, state) { final discount = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) { if (discountModel == null) { return 0; } return discountModel.value! .replaceAll('.00', '') .toIntegerFromText; }); final price = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => products.fold( 0, (previousValue, element) => previousValue + (element.product.price! * element.quantity), ), ); final tax = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => tax, ); final subTotal = price - (discount / 100 * price); final totalDiscount = discount / 100 * price; final finalTax = subTotal * (tax / 100); List items = state.maybeWhen( orElse: () => [], loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => products, ); final totalQty = items.fold( 0, (previousValue, element) => previousValue + element.quantity, ); final orderType = state.maybeWhen( orElse: () => OrderType.dineIn, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => orderType, ); return Flexible( child: Button.filled( onPressed: () async { if (selectedPaymentMethod == null) { ScaffoldMessenger.of(context) .showSnackBar( const SnackBar( content: Text( 'Please select a payment method'), backgroundColor: Colors.red, ), ); return; } final paymentMethodName = selectedPaymentMethod?.name ?.toLowerCase() ?? ''; log("Selected payment method: ${selectedPaymentMethod?.name} (normalized: $paymentMethodName)"); if (paymentMethodName == 'cash' || paymentMethodName == 'tunai' || paymentMethodName == 'uang tunai' || paymentMethodName == 'cash payment') { // context.read().add( // OrderEvent.order( // items, // discount, // discountAmountFinal, // finalTax.toInt(), // 0, // totalPriceController.text // .toIntegerFromText, // customerController.text, // widget.table?.id ?? 0, // 'completed', // 'paid', // selectedPaymentMethod // ?.name ?? // 'Cash', // totalPriceFinal, // orderType)); await showDialog( context: context, barrierDismissible: false, builder: (context) => SuccessPaymentDialog( isTablePaymentPage: true, data: items, totalQty: totalQty, totalPrice: totalPriceFinal.toInt(), totalTax: finalTax.toInt(), totalDiscount: totalDiscount.toInt(), subTotal: subTotal.toInt(), normalPrice: price, totalService: 0, draftName: customerController.text, ), ); await _handlePostPaymentCleanup(); } else { log("Processing non-cash payment: ${selectedPaymentMethod?.name}"); // context.read().add( // OrderEvent.order( // items, // discount, // discountAmountFinal, // finalTax.toInt(), // 0, // totalPriceController.text // .toIntegerFromText, // customerController.text, // widget.table?.id ?? 0, // 'completed', // 'paid', // selectedPaymentMethod // ?.name ?? // 'Unknown Payment Method', // totalPriceFinal, // orderType)); await showDialog( context: context, barrierDismissible: false, builder: (context) => SuccessPaymentDialog( isTablePaymentPage: true, data: items, totalQty: totalQty, totalPrice: totalPriceFinal.toInt(), totalTax: finalTax.toInt(), totalDiscount: totalDiscount.toInt(), subTotal: subTotal.toInt(), normalPrice: price, totalService: 0, draftName: customerController.text, ), ); // Handle post-payment cleanup await _handlePostPaymentCleanup(); } }, label: 'Bayar', ), ); }, ), ), ], ), ), ), ), ], ), ), ), ], ), ), ), ); } }