diff --git a/lib/presentation/home/dialog/save_dialog.dart b/lib/presentation/home/dialog/save_dialog.dart new file mode 100644 index 0000000..05f7be6 --- /dev/null +++ b/lib/presentation/home/dialog/save_dialog.dart @@ -0,0 +1,76 @@ +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:flutter/material.dart'; + +class SaveDialog extends StatelessWidget { + const SaveDialog({super.key}); + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Pilih Aksi', + subtitle: 'Lanjutkan proses pesanan atau simpan untuk nanti.', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: Column( + children: [ + _item( + icon: Icons.schedule_outlined, + title: 'Bayar Nanti', + subtitle: 'Simpan pesanan dan bayar nanti', + onTap: () {}, + ), + SpaceHeight(16.0), + _item( + icon: Icons.shopping_cart_checkout_outlined, + title: 'Tambahkan Pesanan', + subtitle: 'ambah item ke daftar pesanan', + onTap: () {}, + ), + ], + ), + ); + } + + Widget _item({ + required IconData icon, + required String title, + required String subtitle, + required Function() onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.0), + border: Border.all( + color: AppColors.grey, + width: 1.0, + ), + ), + child: Row( + children: [ + Icon(icon, color: AppColors.primary, size: 26.0), + SpaceWidth(12.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle(fontWeight: FontWeight.w600), + ), + Text( + subtitle, + style: const TextStyle(color: Colors.grey), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/home/pages/confirm_payment_page-new.dart.backup b/lib/presentation/home/pages/confirm_payment_page-new.dart.backup new file mode 100644 index 0000000..424ef94 --- /dev/null +++ b/lib/presentation/home/pages/confirm_payment_page-new.dart.backup @@ -0,0 +1,1708 @@ +// 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/build_context_ext.dart'; +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/core/extensions/string_ext.dart'; +import 'package:enaklo_pos/data/models/response/table_model.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/save_order_dialog.dart'; +import 'package:enaklo_pos/data/models/response/payment_methods_response_model.dart'; +import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; +import 'package:enaklo_pos/presentation/table/models/draft_order_item.dart'; + +import '../../../core/components/buttons.dart'; +import '../../../core/components/spaces.dart'; +import '../../../core/constants/colors.dart'; +import '../bloc/checkout/checkout_bloc.dart'; +import '../widgets/order_menu.dart'; +import '../widgets/success_payment_dialog.dart'; +import '../widgets/order_type_selector.dart'; + +class ConfirmPaymentPage extends StatefulWidget { + final bool isTable; + final TableModel? table; + + const ConfirmPaymentPage({ + super.key, + required this.isTable, + this.table, + }); + + @override + State createState() => _ConfirmPaymentPageState(); +} + +class _ConfirmPaymentPageState extends State { + final totalPriceController = TextEditingController(); + final customerController = TextEditingController(); + bool isPayNow = true; + bool isAddToOrder = false; + PaymentMethod? selectedPaymentMethod; + TableModel? selectTable; + int discountAmount = 0; + int priceValue = 0; + int uangPas = 0; + int uangPas2 = 0; + int uangPas3 = 0; + + // int discountAmountValue = 0; + int totalPriceFinal = 0; + + // int taxFinal = 0; + // int serviceChargeFinal = 0; + + @override + void initState() { + // Fetch available tables by default + context + .read() + .add(GetTableStatusEvent.getTablesStatus('available')); + context + .read() + .add(PaymentMethodsEvent.fetchPaymentMethods()); + + // Set a default payment method in case API fails + selectedPaymentMethod = PaymentMethod( + id: 1, + name: 'Cash', + description: 'Cash payment', + isActive: true, + sortOrder: 1, + ); + // if (selectTable == null && widget.table != null) { + // selectTable = tables.firstWhere( + // (t) => t.id == widget.table!.id, + // orElse: () => null, + // ); + // } + if (widget.table != null) { + // selectTable = TableModel( + // tableNumber: widget.table!.tableNumber, + // startTime: widget.table!.startTime, + // status: widget.table!.status, + // orderId: widget.table!.orderId, + // paymentAmount: widget.table!.paymentAmount, + // position: widget.table!.position, + // ); + } + super.initState(); + } + + @override + void dispose() { + totalPriceController.dispose(); + customerController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Hero( + tag: 'payment_confirmation_screen', + child: Scaffold( + backgroundColor: AppColors.white, + body: Row( + children: [ + Expanded( + flex: 2, + child: Align( + alignment: Alignment.topCenter, + child: SingleChildScrollView( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Konfirmasi', + style: TextStyle( + color: AppColors.primary, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + Text( + widget.isTable + ? 'Orders Table ${widget.table?.tableName}' + : 'Orders #1', + 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(12.0), + itemCount: products.length, + ); + }, + ); + }, + ), + const SpaceHeight(8.0), + const Divider(), + const SpaceHeight(4.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! + .toIntegerFromText * + element.quantity), + )); + return Text( + price.currencyFormatRp, + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(4.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) { + if (discountModel == null) { + return 0; + } + return discountModel.value! + .replaceAll('.00', '') + .toIntegerFromText; + }); + + final subTotal = state.maybeWhen( + orElse: () => 0, + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) => + products.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! + .toIntegerFromText * + element.quantity), + )); + + final finalDiscount = discount / 100 * subTotal; + discountAmount = finalDiscount.toInt(); + return Text( + '$discount % (${finalDiscount.toInt().currencyFormatRp})', + style: TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(4.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! + .toIntegerFromText * + element.quantity), + ), + ); + + 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 subTotal = + price - (discount / 100 * price); + final finalTax = subTotal * (tax / 100); + // final finalDiscount = discount / 100 * subTotal; + // discountAmountValue = finalDiscount.toInt(); + // taxFinal = finalTax.toInt(); + return Text( + '$tax % (${finalTax.toInt().currencyFormatRp})', + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(4.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Biaya Layanan', + style: TextStyle(color: AppColors.grey), + ), + BlocBuilder( + builder: (context, state) { + final serviceCharge = state.maybeWhen( + orElse: () => 0, + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) => + serviceCharge, + ); + + 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! + .toIntegerFromText * + element.quantity), + ), + ); + + final nominalServiceCharge = + (serviceCharge / 100) * + (price - discountAmount); + + // serviceChargeFinal = + // nominalServiceCharge.toInt(); + return Text( + '$serviceCharge % (${nominalServiceCharge.toInt().currencyFormatRp})', + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(10.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! + .toIntegerFromText * + element.quantity), + ), + ); + + 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 tax = state.maybeWhen( + orElse: () => 0, + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) => + tax, + ); + + final serviceCharge = state.maybeWhen( + orElse: () => 0, + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) => + serviceCharge, + ); + + final subTotal = + price - (discount / 100 * price); + final finalTax = subTotal * (tax / 100); + final service = + (serviceCharge / 100) * subTotal; + final total = subTotal + finalTax + service; + priceValue = total.toInt(); + totalPriceController.text = + total.ceil().currencyFormatRpV2; + uangPas = total.ceil(); + uangPas2 = uangPas ~/ 50000 * 50000 + 50000; + uangPas3 = uangPas ~/ 50000 * 50000 + 100000; + totalPriceFinal = total.ceil(); + // log("totalPriceFinal: $totalPriceFinal"); + return Text( + total.ceil().currencyFormatRp, + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + fontSize: 16, + ), + ); + }, + ), + ], + ), + ], + ), + ), + ), + ), + Expanded( + flex: 3, + child: Align( + alignment: Alignment.topCenter, + child: ListView( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.isTable != true) ...[ + const Text( + 'Pembayaran', + style: TextStyle( + color: AppColors.primary, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(16.0), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + isPayNow && !isAddToOrder + ? Button.filled( + height: 52.0, + width: 200.0, + onPressed: () { + isPayNow = true; + isAddToOrder = false; + setState(() {}); + }, + label: 'Bayar Sekarang', + ) + : Button.outlined( + width: 200.0, + height: 52.0, + onPressed: () { + isPayNow = true; + isAddToOrder = false; + setState(() {}); + }, + label: 'Bayar Sekarang'), + SpaceWidth(8), + !isPayNow && !isAddToOrder + ? Button.filled( + width: 200.0, + height: 52.0, + onPressed: () async { + print( + "🔘 Bayar Nanti button pressed (filled)"); + print( + "🔘 Fetching available tables for Bayar Nanti"); + isPayNow = false; + isAddToOrder = false; + + // Debug: Check all tables first + final allTables = + await ProductLocalDatasource + .instance + .getAllTable(); + print( + "🔘 All tables in database: ${allTables.length}"); + allTables.forEach((table) { + print( + "🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}"); + }); + + // Fetch available tables for Bayar Nanti + context + .read() + .add( + GetTableStatusEvent + .getTablesStatus( + 'available'), + ); + setState(() {}); + }, + label: 'Bayar Nanti', + ) + : Button.outlined( + width: 200.0, + height: 52.0, + onPressed: () async { + print( + "🔘 Bayar Nanti button pressed (outlined)"); + print( + "🔘 Fetching available tables for Bayar Nanti"); + isPayNow = false; + isAddToOrder = false; + + // Debug: Check all tables first + final allTables = + await ProductLocalDatasource + .instance + .getAllTable(); + print( + "🔘 All tables in database: ${allTables.length}"); + allTables.forEach((table) { + print( + "🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}"); + }); + + // Fetch available tables for Bayar Nanti + context + .read() + .add( + GetTableStatusEvent + .getTablesStatus( + 'available'), + ); + setState(() {}); + }, + label: 'Bayar Nanti'), + SpaceWidth(8), + isAddToOrder + ? Button.filled( + width: 200.0, + height: 52.0, + onPressed: () { + isPayNow = false; + isAddToOrder = true; + setState(() {}); + }, + label: 'Tambah ke Pesanan', + ) + : Button.outlined( + width: 200.0, + height: 52.0, + onPressed: () { + print( + "🔘 Tambah button pressed (outlined)"); + print( + "🔘 Fetching occupied tables for Tambah ke Pesanan"); + isPayNow = false; + isAddToOrder = true; + // Fetch occupied tables for Tambah ke Pesanan + context + .read() + .add( + GetTableStatusEvent + .getTablesStatus( + 'occupied'), + ); + setState(() {}); + }, + label: 'Tambah ke Pesanan', + ), + ], + ), + ), + ], + const SpaceHeight(8.0), + if (!isPayNow && !isAddToOrder) ...[ + const Divider(), + const SpaceHeight(8.0), + const Text( + 'Pilih Meja', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SpaceHeight(12.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => + const CircularProgressIndicator(), + success: (tables) { + print( + "🔘 Tables fetched: ${tables.length} tables"); + print( + "🔘 Table statuses: ${tables.map((t) => '${t.tableName}: ${t.status}').join(', ')}"); + print( + "🔘 Current mode: ${!isPayNow && !isAddToOrder ? 'Pay Later' : isAddToOrder ? 'Add to Order' : 'Pay Now'}"); + // No need to filter since we're fetching the correct tables directly + final availableTables = tables; + + if (selectTable == null && + availableTables.isNotEmpty) { + selectTable = availableTables.first; + } + + if (availableTables.isEmpty) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.orange[50], + borderRadius: + BorderRadius.circular(16), + border: Border.all( + color: Colors.orange, + width: 1, + ), + ), + child: const Text( + 'Tidak ada meja yang tersedia. Silakan pilih opsi lain.', + style: + TextStyle(color: Colors.orange), + ), + ); + } + + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Theme.of(context).primaryColor, + width: 2, + ), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: selectTable, + onChanged: (TableModel? newValue) { + setState(() { + selectTable = newValue; + }); + }, + items: availableTables + .map< + DropdownMenuItem>( + (TableModel value) => + DropdownMenuItem< + TableModel>( + value: value, + child: Text(value.tableName), + ), + ) + .toList(), + ), + ), + ); + }, + ); + }), + ], + const SpaceHeight(8.0), + if (isAddToOrder) ...[ + const Divider(), + const SpaceHeight(8.0), + const Text( + 'Pilih Meja yang Sudah Ada Pesanan', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SpaceHeight(12.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => + const CircularProgressIndicator(), + success: (tables) { + print( + "🔘 Add to Order - Tables fetched: ${tables.length} tables"); + print( + "🔘 Add to Order - Table statuses: ${tables.map((t) => '${t.tableName}: ${t.status}').join(', ')}"); + // No need to filter since we're fetching occupied tables directly + final occupiedTables = tables; + + if (selectTable == null && + occupiedTables.isNotEmpty) { + selectTable = occupiedTables.first; + } + + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Theme.of(context).primaryColor, + width: 2, + ), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: selectTable, + onChanged: (TableModel? newValue) { + setState(() { + selectTable = newValue; + }); + }, + items: occupiedTables + .map< + DropdownMenuItem>( + (TableModel value) => + DropdownMenuItem< + TableModel>( + value: value, + child: Text( + '${value.tableName} (Occupied)'), + ), + ) + .toList(), + ), + ), + ); + }, + ); + }), + ], + if (!isAddToOrder) ...[ + const SpaceHeight(8.0), + const Divider(), + const SpaceHeight(8.0), + const Text( + 'Customer', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SpaceHeight(12.0), + TextFormField( + controller: customerController, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + ), + hintText: 'Nama Customer', + ), + textCapitalization: TextCapitalization.words, + ), + ], + const SpaceHeight(8.0), + const Divider(), + const SpaceHeight(8.0), + const OrderTypeSelector(), + const SpaceHeight(8.0), + if (isPayNow) ...[ + 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: Tooltip( + message: method.description ?? + 'No description available', + 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), + BlocBuilder( + builder: (context, state) { + return TextFormField( + controller: totalPriceController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(8.0), + ), + hintText: 'Total harga', + ), + onChanged: (value) { + priceValue = value.toIntegerFromText; + final int newValue = + value.toIntegerFromText; + totalPriceController.text = + newValue.currencyFormatRp; + totalPriceController.selection = + TextSelection.fromPosition( + TextPosition( + offset: totalPriceController + .text.length)); + }, + ); + }, + ), + const SpaceHeight(20.0), + BlocBuilder( + builder: (context, state) { + return Row( + children: [ + Button.filled( + width: 150.0, + onPressed: () { + totalPriceController.text = uangPas + .toString() + .currencyFormatRpV2; + priceValue = uangPas; + }, + label: 'UANG PAS', + ), + const SpaceWidth(20.0), + Button.filled( + width: 150.0, + onPressed: () { + totalPriceController.text = uangPas2 + .toString() + .currencyFormatRpV2; + priceValue = uangPas2; + }, + label: uangPas2 + .toString() + .currencyFormatRpV2, + ), + const SpaceWidth(20.0), + Button.filled( + width: 150.0, + onPressed: () { + totalPriceController.text = uangPas3 + .toString() + .currencyFormatRpV2; + priceValue = uangPas3; + }, + label: uangPas3 + .toString() + .currencyFormatRpV2, + ), + ], + ); + }, + ), + ] + ], + ), + ), + 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), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + savedDraftOrder: (orderDraftId) { + log("PRICEVALUE: ${priceValue}"); + + final newTabel = TableModel( + id: widget.isTable + ? widget.table!.id + : selectTable?.id, + tableName: widget.isTable + ? widget.table!.tableName + : selectTable?.tableName ?? + '0', + status: 'occupied', + paymentAmount: priceValue, + orderId: orderDraftId, + startTime: DateTime.now() + .toIso8601String(), + position: widget.isTable + ? widget.table!.position + : selectTable!.position); + log('new tabel: ${newTabel.toMap()}'); + context + .read() + .add(StatusTableEvent.statusTabel( + newTabel, + )); + }); + }, + 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! + .toIntegerFromText * + element.quantity), + ), + ); + + 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 orderType = state.maybeWhen( + orElse: () => OrderType.dineIn, + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) => + orderType, + ); + + final subTotal = + price - (discount / 100 * price); + final totalDiscount = + discount / 100 * price; + final finalTax = subTotal * (tax / 100); + final totalServiceCharge = + (serviceCharge / 100) * price; + + 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, + ); + + return Flexible( + child: Button.filled( + onPressed: () async { + print("🔘 Payment button pressed"); + print("🔘 isPayNow: $isPayNow"); + print( + "🔘 isAddToOrder: $isAddToOrder"); + print( + "🔘 selectedPaymentMethod: ${selectedPaymentMethod?.name}"); + if (selectedPaymentMethod == null) { + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + 'Please select a payment method'), + backgroundColor: Colors.red, + ), + ); + return; + } + + if (widget.isTable) { + log("discountAmountValue: $totalDiscount"); + context.read().add( + CheckoutEvent + .saveDraftOrder( + widget.isTable == true + ? widget.table!.id! + : selectTable!.id!, + customerController.text, + totalDiscount.toInt(), + ), + ); + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + SaveOrderDialog( + data: items, + totalQty: totalQty, + totalPrice: totalPriceFinal, + totalTax: finalTax.toInt(), + totalDiscount: + totalDiscount.toInt(), + subTotal: subTotal.toInt(), + normalPrice: price, + table: widget.table!, + draftName: + customerController.text, + ), + ); + } else if (isPayNow) { + log("🔘 Entering Pay Now flow"); + // context.read().add( + // OrderEvent.addPaymentMethod( + // items, + // totalPrice, + // finalTax, + // discount != null + // ? discount.value + // .replaceAll( + // '.00', '') + // .toIntegerFromText + // : 0, + // finalDiscountAmount, + // finalService, + // subTotal, + // totalPriceController.text + // .toIntegerFromText, + // auth?.user.name ?? '-', + // totalQuantity, + // auth?.user.id ?? 1, + // isCash + // ? 'Cash' + // : 'QR Pay')); + final paymentMethodName = + selectedPaymentMethod?.name + ?.toLowerCase() ?? + ''; + log("Selected payment method: ${selectedPaymentMethod?.name} (normalized: $paymentMethodName)"); + if (paymentMethodName == 'cash' || + paymentMethodName == + 'tunai' || + paymentMethodName == + 'uang tunai' || + paymentMethodName == + 'cash payment') { + log("🔘 Payment method is cash, proceeding with OrderBloc call"); + log("discountAmountValue: $totalDiscount"); + log("💳 About to call OrderBloc for cash payment"); + log("💳 OrderBloc instance: ${context.read()}"); + log("💳 About to dispatch OrderEvent.order for cash payment"); + context.read().add( + OrderEvent.order( + items, + discount, + totalDiscount.toInt(), + finalTax.toInt(), + 0, + totalPriceController + .text + .toIntegerFromText, + customerController.text, + 0, + 'completed', + 'paid', + selectedPaymentMethod + ?.name ?? + 'Cash', + totalPriceFinal, + orderType)); + log("💳 OrderEvent.order dispatched for cash payment"); + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + SuccessPaymentDialog( + data: items, + totalQty: totalQty, + totalPrice: totalPriceFinal, + totalTax: finalTax.toInt(), + totalDiscount: + totalDiscount.toInt(), + subTotal: subTotal.toInt(), + normalPrice: price, + totalService: + totalServiceCharge + .toInt(), + draftName: + customerController.text, + ), + ); + } else { + // Process non-cash payment directly without QRIS dialog + log("Processing non-cash payment: ${selectedPaymentMethod?.name}"); + log("💳 About to call OrderBloc for non-cash payment"); + log("💳 OrderBloc instance: ${context.read()}"); + context.read().add( + OrderEvent.order( + items, + discount, + totalDiscount.toInt(), + finalTax.toInt(), + 0, + totalPriceController + .text + .toIntegerFromText, + customerController.text, + 0, + 'completed', + 'paid', + selectedPaymentMethod + ?.name ?? + 'Unknown Payment Method', + totalPriceFinal, + orderType)); + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + SuccessPaymentDialog( + data: items, + totalQty: totalQty, + totalPrice: totalPriceFinal, + totalTax: finalTax.toInt(), + totalDiscount: + totalDiscount.toInt(), + subTotal: subTotal.toInt(), + normalPrice: price, + totalService: + totalServiceCharge + .toInt(), + draftName: + customerController.text, + ), + ); + } + } else if (isAddToOrder) { + // Tambahkan ke pesanan meja yang sudah ada + if (selectTable != null) { + // Ambil draft order yang sudah ada + final existingDraftOrder = + await ProductLocalDatasource + .instance + .getDraftOrderById( + selectTable + ?.orderId ?? + 0); + + if (existingDraftOrder != + null) { + // Convert items ke DraftOrderItem + final newDraftItems = items + .map((item) => + DraftOrderItem( + product: + item.product, + quantity: + item.quantity, + )) + .toList(); + + // Gabungkan dengan pesanan yang sudah ada + final updatedItems = [ + ...existingDraftOrder + .orders, + ...newDraftItems + ]; + + final updatedDraftOrder = + existingDraftOrder + .copyWith( + orders: updatedItems, + totalQuantity: + updatedItems.fold( + 0, + (sum, item) => + sum + + item.quantity), + subTotal: updatedItems.fold< + int>( + 0, + (sum, item) => + sum + + (int.tryParse(item + .product + .price ?? + '0') ?? + 0) * + item.quantity), + ); + + // Update draft order + await ProductLocalDatasource + .instance + .updateDraftOrder( + updatedDraftOrder); + + // Tampilkan dialog sukses + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + SaveOrderDialog( + data: items, + totalQty: totalQty, + totalPrice: + totalPriceFinal, + totalTax: + finalTax.toInt(), + totalDiscount: + totalDiscount.toInt(), + subTotal: + subTotal.toInt(), + normalPrice: price, + table: selectTable!, + draftName: + customerController + .text, + ), + ); + } else { + // Jika tidak ada draft order, buat baru + context + .read() + .add( + CheckoutEvent + .saveDraftOrder( + selectTable!.id!, + customerController + .text, + totalDiscount.toInt(), + ), + ); + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + SaveOrderDialog( + data: items, + totalQty: totalQty, + totalPrice: + totalPriceFinal, + totalTax: + finalTax.toInt(), + totalDiscount: + totalDiscount.toInt(), + subTotal: + subTotal.toInt(), + normalPrice: price, + table: selectTable!, + draftName: + customerController + .text, + ), + ); + } + } else { + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + 'Pilih meja terlebih dahulu'), + backgroundColor: Colors.red, + ), + ); + } + } else { + context.read().add( + CheckoutEvent + .saveDraftOrder( + widget.isTable == true + ? widget.table!.id! + : selectTable!.id!, + customerController.text, + totalDiscount.toInt(), + ), + ); + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + SaveOrderDialog( + data: items, + totalQty: totalQty, + totalPrice: totalPriceFinal, + totalTax: finalTax.toInt(), + totalDiscount: + totalDiscount.toInt(), + subTotal: subTotal.toInt(), + normalPrice: price, + table: selectTable!, + draftName: + customerController.text, + ), + ); + } + }, + label: isPayNow + ? 'Bayar' + : isAddToOrder + ? 'Simpan' + : 'Simpan Order', + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/home/pages/confirm_payment_page.dart b/lib/presentation/home/pages/confirm_payment_page.dart index 424ef94..eacdb66 100644 --- a/lib/presentation/home/pages/confirm_payment_page.dart +++ b/lib/presentation/home/pages/confirm_payment_page.dart @@ -1,31 +1,24 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:developer'; +import 'package:enaklo_pos/core/components/dashed_divider.dart'; +import 'package:enaklo_pos/presentation/home/dialog/save_dialog.dart'; +import 'package:enaklo_pos/presentation/home/widgets/confirm_payment_title.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:enaklo_pos/core/extensions/string_ext.dart'; import 'package:enaklo_pos/data/models/response/table_model.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/save_order_dialog.dart'; import 'package:enaklo_pos/data/models/response/payment_methods_response_model.dart'; -import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; -import 'package:enaklo_pos/presentation/table/models/draft_order_item.dart'; import '../../../core/components/buttons.dart'; import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; import '../bloc/checkout/checkout_bloc.dart'; import '../widgets/order_menu.dart'; -import '../widgets/success_payment_dialog.dart'; -import '../widgets/order_type_selector.dart'; class ConfirmPaymentPage extends StatefulWidget { final bool isTable; @@ -114,45 +107,29 @@ class _ConfirmPaymentPageState extends State { body: Row( children: [ Expanded( - flex: 2, + flex: 3, child: Align( alignment: Alignment.topCenter, - child: SingleChildScrollView( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Konfirmasi', - style: TextStyle( - color: AppColors.primary, - fontSize: 20, - fontWeight: FontWeight.w600, - ), - ), - Text( - widget.isTable - ? 'Orders Table ${widget.table?.tableName}' - : 'Orders #1', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ], + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ConfirmPaymentTitle( + title: 'Konfirmasi', + subtitle: widget.isTable + ? 'Orders Table ${widget.table?.tableName}' + : 'Orders #1', + ), + Container( + padding: const EdgeInsets.all(16.0).copyWith(bottom: 8), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, ), - ], + ), ), - const SpaceHeight(8.0), - const Divider(), - const SpaceHeight(24.0), - const Row( + child: const Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( @@ -189,10 +166,9 @@ class _ConfirmPaymentPageState extends State { ), ], ), - const SpaceHeight(8), - const Divider(), - const SpaceHeight(8), - BlocBuilder( + ), + Expanded( + child: BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => const Center( @@ -215,7 +191,8 @@ class _ConfirmPaymentPageState extends State { } return ListView.separated( shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(16.0) + .copyWith(top: 8.0), itemBuilder: (context, index) => OrderMenu(data: products[index]), separatorBuilder: (context, index) => @@ -226,1027 +203,32 @@ class _ConfirmPaymentPageState extends State { ); }, ), - const SpaceHeight(8.0), - const Divider(), - const SpaceHeight(4.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! - .toIntegerFromText * - element.quantity), - )); - return Text( - price.currencyFormatRp, - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ); - }, - ), - ], - ), - const SpaceHeight(4.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) { - if (discountModel == null) { - return 0; - } - return discountModel.value! - .replaceAll('.00', '') - .toIntegerFromText; - }); - - final subTotal = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - products.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price! - .toIntegerFromText * - element.quantity), - )); - - final finalDiscount = discount / 100 * subTotal; - discountAmount = finalDiscount.toInt(); - return Text( - '$discount % (${finalDiscount.toInt().currencyFormatRp})', - style: TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ); - }, - ), - ], - ), - const SpaceHeight(4.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! - .toIntegerFromText * - element.quantity), - ), - ); - - 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 subTotal = - price - (discount / 100 * price); - final finalTax = subTotal * (tax / 100); - // final finalDiscount = discount / 100 * subTotal; - // discountAmountValue = finalDiscount.toInt(); - // taxFinal = finalTax.toInt(); - return Text( - '$tax % (${finalTax.toInt().currencyFormatRp})', - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ); - }, - ), - ], - ), - const SpaceHeight(4.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Biaya Layanan', - style: TextStyle(color: AppColors.grey), - ), - BlocBuilder( - builder: (context, state) { - final serviceCharge = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - serviceCharge, - ); - - 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! - .toIntegerFromText * - element.quantity), - ), - ); - - final nominalServiceCharge = - (serviceCharge / 100) * - (price - discountAmount); - - // serviceChargeFinal = - // nominalServiceCharge.toInt(); - return Text( - '$serviceCharge % (${nominalServiceCharge.toInt().currencyFormatRp})', - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ); - }, - ), - ], - ), - const SpaceHeight(10.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! - .toIntegerFromText * - element.quantity), - ), - ); - - 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 tax = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - tax, - ); - - final serviceCharge = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - serviceCharge, - ); - - final subTotal = - price - (discount / 100 * price); - final finalTax = subTotal * (tax / 100); - final service = - (serviceCharge / 100) * subTotal; - final total = subTotal + finalTax + service; - priceValue = total.toInt(); - totalPriceController.text = - total.ceil().currencyFormatRpV2; - uangPas = total.ceil(); - uangPas2 = uangPas ~/ 50000 * 50000 + 50000; - uangPas3 = uangPas ~/ 50000 * 50000 + 100000; - totalPriceFinal = total.ceil(); - // log("totalPriceFinal: $totalPriceFinal"); - return Text( - total.ceil().currencyFormatRp, - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ); - }, - ), - ], - ), - ], - ), - ), - ), - ), - Expanded( - flex: 3, - child: Align( - alignment: Alignment.topCenter, - child: ListView( - children: [ - SingleChildScrollView( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.isTable != true) ...[ - const Text( - 'Pembayaran', - style: TextStyle( - color: AppColors.primary, - fontSize: 20, - fontWeight: FontWeight.w600, - ), - ), - const SpaceHeight(16.0), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - isPayNow && !isAddToOrder - ? Button.filled( - height: 52.0, - width: 200.0, - onPressed: () { - isPayNow = true; - isAddToOrder = false; - setState(() {}); - }, - label: 'Bayar Sekarang', - ) - : Button.outlined( - width: 200.0, - height: 52.0, - onPressed: () { - isPayNow = true; - isAddToOrder = false; - setState(() {}); - }, - label: 'Bayar Sekarang'), - SpaceWidth(8), - !isPayNow && !isAddToOrder - ? Button.filled( - width: 200.0, - height: 52.0, - onPressed: () async { - print( - "🔘 Bayar Nanti button pressed (filled)"); - print( - "🔘 Fetching available tables for Bayar Nanti"); - isPayNow = false; - isAddToOrder = false; - - // Debug: Check all tables first - final allTables = - await ProductLocalDatasource - .instance - .getAllTable(); - print( - "🔘 All tables in database: ${allTables.length}"); - allTables.forEach((table) { - print( - "🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}"); - }); - - // Fetch available tables for Bayar Nanti - context - .read() - .add( - GetTableStatusEvent - .getTablesStatus( - 'available'), - ); - setState(() {}); - }, - label: 'Bayar Nanti', - ) - : Button.outlined( - width: 200.0, - height: 52.0, - onPressed: () async { - print( - "🔘 Bayar Nanti button pressed (outlined)"); - print( - "🔘 Fetching available tables for Bayar Nanti"); - isPayNow = false; - isAddToOrder = false; - - // Debug: Check all tables first - final allTables = - await ProductLocalDatasource - .instance - .getAllTable(); - print( - "🔘 All tables in database: ${allTables.length}"); - allTables.forEach((table) { - print( - "🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}"); - }); - - // Fetch available tables for Bayar Nanti - context - .read() - .add( - GetTableStatusEvent - .getTablesStatus( - 'available'), - ); - setState(() {}); - }, - label: 'Bayar Nanti'), - SpaceWidth(8), - isAddToOrder - ? Button.filled( - width: 200.0, - height: 52.0, - onPressed: () { - isPayNow = false; - isAddToOrder = true; - setState(() {}); - }, - label: 'Tambah ke Pesanan', - ) - : Button.outlined( - width: 200.0, - height: 52.0, - onPressed: () { - print( - "🔘 Tambah button pressed (outlined)"); - print( - "🔘 Fetching occupied tables for Tambah ke Pesanan"); - isPayNow = false; - isAddToOrder = true; - // Fetch occupied tables for Tambah ke Pesanan - context - .read() - .add( - GetTableStatusEvent - .getTablesStatus( - 'occupied'), - ); - setState(() {}); - }, - label: 'Tambah ke Pesanan', - ), - ], - ), - ), - ], - const SpaceHeight(8.0), - if (!isPayNow && !isAddToOrder) ...[ - const Divider(), - const SpaceHeight(8.0), - const Text( - 'Pilih Meja', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SpaceHeight(12.0), - BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => - const CircularProgressIndicator(), - success: (tables) { - print( - "🔘 Tables fetched: ${tables.length} tables"); - print( - "🔘 Table statuses: ${tables.map((t) => '${t.tableName}: ${t.status}').join(', ')}"); - print( - "🔘 Current mode: ${!isPayNow && !isAddToOrder ? 'Pay Later' : isAddToOrder ? 'Add to Order' : 'Pay Now'}"); - // No need to filter since we're fetching the correct tables directly - final availableTables = tables; - - if (selectTable == null && - availableTables.isNotEmpty) { - selectTable = availableTables.first; - } - - if (availableTables.isEmpty) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.orange[50], - borderRadius: - BorderRadius.circular(16), - border: Border.all( - color: Colors.orange, - width: 1, - ), - ), - child: const Text( - 'Tidak ada meja yang tersedia. Silakan pilih opsi lain.', - style: - TextStyle(color: Colors.orange), - ), - ); - } - - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Theme.of(context).primaryColor, - width: 2, - ), - ), - child: DropdownButtonHideUnderline( - child: DropdownButton( - isExpanded: true, - value: selectTable, - onChanged: (TableModel? newValue) { - setState(() { - selectTable = newValue; - }); - }, - items: availableTables - .map< - DropdownMenuItem>( - (TableModel value) => - DropdownMenuItem< - TableModel>( - value: value, - child: Text(value.tableName), - ), - ) - .toList(), - ), - ), - ); - }, - ); - }), - ], - const SpaceHeight(8.0), - if (isAddToOrder) ...[ - const Divider(), - const SpaceHeight(8.0), - const Text( - 'Pilih Meja yang Sudah Ada Pesanan', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SpaceHeight(12.0), - BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => - const CircularProgressIndicator(), - success: (tables) { - print( - "🔘 Add to Order - Tables fetched: ${tables.length} tables"); - print( - "🔘 Add to Order - Table statuses: ${tables.map((t) => '${t.tableName}: ${t.status}').join(', ')}"); - // No need to filter since we're fetching occupied tables directly - final occupiedTables = tables; - - if (selectTable == null && - occupiedTables.isNotEmpty) { - selectTable = occupiedTables.first; - } - - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Theme.of(context).primaryColor, - width: 2, - ), - ), - child: DropdownButtonHideUnderline( - child: DropdownButton( - isExpanded: true, - value: selectTable, - onChanged: (TableModel? newValue) { - setState(() { - selectTable = newValue; - }); - }, - items: occupiedTables - .map< - DropdownMenuItem>( - (TableModel value) => - DropdownMenuItem< - TableModel>( - value: value, - child: Text( - '${value.tableName} (Occupied)'), - ), - ) - .toList(), - ), - ), - ); - }, - ); - }), - ], - if (!isAddToOrder) ...[ - const SpaceHeight(8.0), - const Divider(), - const SpaceHeight(8.0), - const Text( - 'Customer', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SpaceHeight(12.0), - TextFormField( - controller: customerController, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - ), - hintText: 'Nama Customer', - ), - textCapitalization: TextCapitalization.words, - ), - ], - const SpaceHeight(8.0), - const Divider(), - const SpaceHeight(8.0), - const OrderTypeSelector(), - const SpaceHeight(8.0), - if (isPayNow) ...[ - 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: Tooltip( - message: method.description ?? - 'No description available', - 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), - BlocBuilder( - builder: (context, state) { - return TextFormField( - controller: totalPriceController, - keyboardType: TextInputType.number, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: - BorderRadius.circular(8.0), - ), - hintText: 'Total harga', - ), - onChanged: (value) { - priceValue = value.toIntegerFromText; - final int newValue = - value.toIntegerFromText; - totalPriceController.text = - newValue.currencyFormatRp; - totalPriceController.selection = - TextSelection.fromPosition( - TextPosition( - offset: totalPriceController - .text.length)); - }, - ); - }, - ), - const SpaceHeight(20.0), - BlocBuilder( - builder: (context, state) { - return Row( - children: [ - Button.filled( - width: 150.0, - onPressed: () { - totalPriceController.text = uangPas - .toString() - .currencyFormatRpV2; - priceValue = uangPas; - }, - label: 'UANG PAS', - ), - const SpaceWidth(20.0), - Button.filled( - width: 150.0, - onPressed: () { - totalPriceController.text = uangPas2 - .toString() - .currencyFormatRpV2; - priceValue = uangPas2; - }, - label: uangPas2 - .toString() - .currencyFormatRpV2, - ), - const SpaceWidth(20.0), - Button.filled( - width: 150.0, - onPressed: () { - totalPriceController.text = uangPas3 - .toString() - .currencyFormatRpV2; - priceValue = uangPas3; - }, - label: uangPas3 - .toString() - .currencyFormatRpV2, - ), - ], - ); - }, - ), - ] - ], - ), ), - Align( - alignment: Alignment.bottomCenter, - child: ColoredBox( - color: AppColors.white, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24.0, vertical: 16.0), - child: Row( + Container( + padding: const EdgeInsets.all(16.0), + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: AppColors.grey, + width: 1.0, + ), + ), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Flexible( - child: Button.outlined( - onPressed: () => context.pop(), - label: 'Kembali', + Text( + 'Sub total', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w600, ), ), - const SpaceWidth(8.0), - BlocListener( - listener: (context, state) { - state.maybeWhen( - orElse: () {}, - savedDraftOrder: (orderDraftId) { - log("PRICEVALUE: ${priceValue}"); - - final newTabel = TableModel( - id: widget.isTable - ? widget.table!.id - : selectTable?.id, - tableName: widget.isTable - ? widget.table!.tableName - : selectTable?.tableName ?? - '0', - status: 'occupied', - paymentAmount: priceValue, - orderId: orderDraftId, - startTime: DateTime.now() - .toIso8601String(), - position: widget.isTable - ? widget.table!.position - : selectTable!.position); - log('new tabel: ${newTabel.toMap()}'); - context - .read() - .add(StatusTableEvent.statusTabel( - newTabel, - )); - }); - }, - 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( + BlocBuilder( + builder: (context, state) { + final price = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, @@ -1259,441 +241,584 @@ class _ConfirmPaymentPageState extends State { draftName, orderType) => products.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price! - .toIntegerFromText * - element.quantity), - ), - ); - - 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 orderType = state.maybeWhen( - orElse: () => OrderType.dineIn, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - orderType, - ); - - final subTotal = - price - (discount / 100 * price); - final totalDiscount = - discount / 100 * price; - final finalTax = subTotal * (tax / 100); - final totalServiceCharge = - (serviceCharge / 100) * price; - - 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, - ); - - return Flexible( - child: Button.filled( - onPressed: () async { - print("🔘 Payment button pressed"); - print("🔘 isPayNow: $isPayNow"); - print( - "🔘 isAddToOrder: $isAddToOrder"); - print( - "🔘 selectedPaymentMethod: ${selectedPaymentMethod?.name}"); - if (selectedPaymentMethod == null) { - ScaffoldMessenger.of(context) - .showSnackBar( - const SnackBar( - content: Text( - 'Please select a payment method'), - backgroundColor: Colors.red, - ), - ); - return; - } - - if (widget.isTable) { - log("discountAmountValue: $totalDiscount"); - context.read().add( - CheckoutEvent - .saveDraftOrder( - widget.isTable == true - ? widget.table!.id! - : selectTable!.id!, - customerController.text, - totalDiscount.toInt(), - ), - ); - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SaveOrderDialog( - data: items, - totalQty: totalQty, - totalPrice: totalPriceFinal, - totalTax: finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: subTotal.toInt(), - normalPrice: price, - table: widget.table!, - draftName: - customerController.text, - ), - ); - } else if (isPayNow) { - log("🔘 Entering Pay Now flow"); - // context.read().add( - // OrderEvent.addPaymentMethod( - // items, - // totalPrice, - // finalTax, - // discount != null - // ? discount.value - // .replaceAll( - // '.00', '') - // .toIntegerFromText - // : 0, - // finalDiscountAmount, - // finalService, - // subTotal, - // totalPriceController.text - // .toIntegerFromText, - // auth?.user.name ?? '-', - // totalQuantity, - // auth?.user.id ?? 1, - // isCash - // ? 'Cash' - // : 'QR Pay')); - final paymentMethodName = - selectedPaymentMethod?.name - ?.toLowerCase() ?? - ''; - log("Selected payment method: ${selectedPaymentMethod?.name} (normalized: $paymentMethodName)"); - if (paymentMethodName == 'cash' || - paymentMethodName == - 'tunai' || - paymentMethodName == - 'uang tunai' || - paymentMethodName == - 'cash payment') { - log("🔘 Payment method is cash, proceeding with OrderBloc call"); - log("discountAmountValue: $totalDiscount"); - log("💳 About to call OrderBloc for cash payment"); - log("💳 OrderBloc instance: ${context.read()}"); - log("💳 About to dispatch OrderEvent.order for cash payment"); - context.read().add( - OrderEvent.order( - items, - discount, - totalDiscount.toInt(), - finalTax.toInt(), - 0, - totalPriceController - .text - .toIntegerFromText, - customerController.text, - 0, - 'completed', - 'paid', - selectedPaymentMethod - ?.name ?? - 'Cash', - totalPriceFinal, - orderType)); - log("💳 OrderEvent.order dispatched for cash payment"); - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SuccessPaymentDialog( - data: items, - totalQty: totalQty, - totalPrice: totalPriceFinal, - totalTax: finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: subTotal.toInt(), - normalPrice: price, - totalService: - totalServiceCharge - .toInt(), - draftName: - customerController.text, - ), - ); - } else { - // Process non-cash payment directly without QRIS dialog - log("Processing non-cash payment: ${selectedPaymentMethod?.name}"); - log("💳 About to call OrderBloc for non-cash payment"); - log("💳 OrderBloc instance: ${context.read()}"); - context.read().add( - OrderEvent.order( - items, - discount, - totalDiscount.toInt(), - finalTax.toInt(), - 0, - totalPriceController - .text - .toIntegerFromText, - customerController.text, - 0, - 'completed', - 'paid', - selectedPaymentMethod - ?.name ?? - 'Unknown Payment Method', - totalPriceFinal, - orderType)); - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SuccessPaymentDialog( - data: items, - totalQty: totalQty, - totalPrice: totalPriceFinal, - totalTax: finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: subTotal.toInt(), - normalPrice: price, - totalService: - totalServiceCharge - .toInt(), - draftName: - customerController.text, - ), - ); - } - } else if (isAddToOrder) { - // Tambahkan ke pesanan meja yang sudah ada - if (selectTable != null) { - // Ambil draft order yang sudah ada - final existingDraftOrder = - await ProductLocalDatasource - .instance - .getDraftOrderById( - selectTable - ?.orderId ?? - 0); - - if (existingDraftOrder != - null) { - // Convert items ke DraftOrderItem - final newDraftItems = items - .map((item) => - DraftOrderItem( - product: - item.product, - quantity: - item.quantity, - )) - .toList(); - - // Gabungkan dengan pesanan yang sudah ada - final updatedItems = [ - ...existingDraftOrder - .orders, - ...newDraftItems - ]; - - final updatedDraftOrder = - existingDraftOrder - .copyWith( - orders: updatedItems, - totalQuantity: - updatedItems.fold( - 0, - (sum, item) => - sum + - item.quantity), - subTotal: updatedItems.fold< - int>( - 0, - (sum, item) => - sum + - (int.tryParse(item - .product - .price ?? - '0') ?? - 0) * - item.quantity), - ); - - // Update draft order - await ProductLocalDatasource - .instance - .updateDraftOrder( - updatedDraftOrder); - - // Tampilkan dialog sukses - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SaveOrderDialog( - data: items, - totalQty: totalQty, - totalPrice: - totalPriceFinal, - totalTax: - finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: - subTotal.toInt(), - normalPrice: price, - table: selectTable!, - draftName: - customerController - .text, - ), - ); - } else { - // Jika tidak ada draft order, buat baru - context - .read() - .add( - CheckoutEvent - .saveDraftOrder( - selectTable!.id!, - customerController - .text, - totalDiscount.toInt(), - ), - ); - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SaveOrderDialog( - data: items, - totalQty: totalQty, - totalPrice: - totalPriceFinal, - totalTax: - finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: - subTotal.toInt(), - normalPrice: price, - table: selectTable!, - draftName: - customerController - .text, - ), - ); - } - } else { - ScaffoldMessenger.of(context) - .showSnackBar( - const SnackBar( - content: Text( - 'Pilih meja terlebih dahulu'), - backgroundColor: Colors.red, - ), - ); - } - } else { - context.read().add( - CheckoutEvent - .saveDraftOrder( - widget.isTable == true - ? widget.table!.id! - : selectTable!.id!, - customerController.text, - totalDiscount.toInt(), - ), - ); - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SaveOrderDialog( - data: items, - totalQty: totalQty, - totalPrice: totalPriceFinal, - totalTax: finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: subTotal.toInt(), - normalPrice: price, - table: selectTable!, - draftName: - customerController.text, - ), - ); - } - }, - label: isPayNow - ? 'Bayar' - : isAddToOrder - ? 'Simpan' - : 'Simpan Order', - ), - ); - }, - ), + 0, + (previousValue, element) => + previousValue + + (element.product.price! + .toIntegerFromText * + element.quantity), + )); + return Text( + price.currencyFormatRp, + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w600, + ), + ); + }, ), ], ), + const SpaceHeight(8.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Pajak PB1', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w400, + ), + ), + 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! + .toIntegerFromText * + element.quantity), + ), + ); + + 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 subTotal = + price - (discount / 100 * price); + final finalTax = subTotal * (tax / 100); + // final finalDiscount = discount / 100 * subTotal; + // discountAmountValue = finalDiscount.toInt(); + // taxFinal = finalTax.toInt(); + return Text( + '$tax % (${finalTax.toInt().currencyFormatRp})', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w500, + ), + ); + }, + ), + ], + ), + const SpaceHeight(8.0), + DashedDivider( + color: AppColors.grey, + ), + const SpaceHeight(8.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Total', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + 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! + .toIntegerFromText * + element.quantity), + ), + ); + + 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 tax = state.maybeWhen( + orElse: () => 0, + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) => + tax, + ); + + final serviceCharge = state.maybeWhen( + orElse: () => 0, + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType) => + serviceCharge, + ); + + final subTotal = + price - (discount / 100 * price); + final finalTax = subTotal * (tax / 100); + final service = + (serviceCharge / 100) * subTotal; + final total = subTotal + finalTax + service; + priceValue = total.toInt(); + totalPriceController.text = + total.ceil().currencyFormatRpV2; + uangPas = total.ceil(); + uangPas2 = uangPas ~/ 50000 * 50000 + 50000; + uangPas3 = + uangPas ~/ 50000 * 50000 + 100000; + totalPriceFinal = total.ceil(); + // log("totalPriceFinal: $totalPriceFinal"); + return Text( + total.ceil().currencyFormatRp, + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ); + }, + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + SpaceWidth(2), + Expanded( + flex: 3, + child: Align( + alignment: Alignment.topCenter, + child: Column( + children: [ + ConfirmPaymentTitle( + title: 'Pembayaran', + isBack: false, + subtitle: 'Silahkan lakukan pembayaran', + ), + Expanded( + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Pelanggan', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(6.0), + TextFormField( + controller: customerController, + decoration: InputDecoration( + hintText: 'Nama Pelanggan', + ), + textCapitalization: + TextCapitalization.words, + ), + ], + ), + ), + Container( + padding: const EdgeInsets.all(16), + width: double.infinity, + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Metode Pembayaran', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + 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< + PaymentMethodsBloc>() + .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 Tooltip( + message: method.description ?? + 'No description available', + child: GestureDetector( + onTap: () { + setState(() { + selectedPaymentMethod = + method; + }); + }, + child: Container( + height: 60, + width: 80, + alignment: Alignment.center, + padding: + const EdgeInsets.all( + 8.0), + decoration: BoxDecoration( + color: isSelected + ? AppColors.primary + : AppColors.white, + border: Border.all( + color: + AppColors.primary, + width: 1.0, + ), + borderRadius: + BorderRadius.circular( + 8.0), + ), + child: Text( + method.name ?? "", + style: TextStyle( + color: isSelected + ? AppColors.white + : AppColors.primary, + fontWeight: + FontWeight.bold, + ), + textAlign: + TextAlign.center, + ), + ), + ), + ); + }).toList(), + ); + }, + ); + }, + ), + ], + ), + ), + if (selectedPaymentMethod?.id == 1) + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Total Bayar', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(8.0), + BlocBuilder( + builder: (context, state) { + return TextFormField( + controller: totalPriceController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(8.0), + ), + hintText: 'Total harga', + ), + onChanged: (value) { + priceValue = + value.toIntegerFromText; + final int newValue = + value.toIntegerFromText; + totalPriceController.text = + newValue.currencyFormatRp; + totalPriceController.selection = + TextSelection.fromPosition( + TextPosition( + offset: + totalPriceController + .text.length)); + }, + ); + }, + ), + const SpaceHeight(20.0), + BlocBuilder( + builder: (context, state) { + return Row( + children: [ + Button.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = + uangPas + .toString() + .currencyFormatRpV2; + priceValue = uangPas; + }, + label: 'UANG PAS', + ), + const SpaceWidth(20.0), + Button.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = + uangPas2 + .toString() + .currencyFormatRpV2; + priceValue = uangPas2; + }, + label: uangPas2 + .toString() + .currencyFormatRpV2, + ), + const SpaceWidth(20.0), + Button.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = + uangPas3 + .toString() + .currencyFormatRpV2; + priceValue = uangPas3; + }, + label: uangPas3 + .toString() + .currencyFormatRpV2, + ), + ], + ); + }, + ), + ], + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + top: BorderSide( + color: AppColors.background, + width: 1.0, + ), ), ), + child: Row( + children: [ + Expanded( + child: Button.outlined( + onPressed: () {}, + label: 'Batalkan', + ), + ), + SpaceWidth(12), + Expanded( + child: Button.filled( + onPressed: () => showDialog( + context: context, + builder: (context) => SaveDialog()), + label: 'Simpan', + ), + ), + SpaceWidth(12), + Expanded( + child: Button.filled( + onPressed: () {}, + label: 'Bayar', + ), + ), + ], + ), ), ], ), diff --git a/lib/presentation/home/widgets/confirm_payment_title.dart b/lib/presentation/home/widgets/confirm_payment_title.dart new file mode 100644 index 0000000..345e048 --- /dev/null +++ b/lib/presentation/home/widgets/confirm_payment_title.dart @@ -0,0 +1,74 @@ +import 'package:enaklo_pos/core/components/components.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:flutter/material.dart'; + +class ConfirmPaymentTitle extends StatelessWidget { + final String title; + final String? subtitle; + final bool isBack; + final List? actionWidget; + const ConfirmPaymentTitle({ + super.key, + required this.title, + this.subtitle, + this.isBack = true, + this.actionWidget, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: context.deviceHeight * 0.1, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, + ), + ), + ), + child: Row( + children: [ + if (isBack) ...[ + GestureDetector( + onTap: () => context.pop(), + child: Icon( + Icons.arrow_back, + color: AppColors.primary, + size: 24, + ), + ), + SpaceWidth(16), + ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + color: AppColors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + if (subtitle != null) ...[ + const SizedBox(height: 4.0), + Text( + subtitle!, + style: + const TextStyle(fontSize: 14.0, color: AppColors.grey), + ), + ] + ], + ), + ), + if (actionWidget != null) ...actionWidget!, + ], + ), + ); + } +}