From 5e9d5040e7df2b7b9afb4214df50c2d3652885e7 Mon Sep 17 00:00:00 2001 From: efrilm Date: Wed, 6 Aug 2025 19:08:59 +0700 Subject: [PATCH] feat: confirm save order --- .../dialog/confirm_save_order_dialog.dart | 410 ++++++++++++++++++ lib/presentation/home/dialog/save_dialog.dart | 37 +- .../home/pages/confirm_payment_page.dart | 20 + 3 files changed, 457 insertions(+), 10 deletions(-) create mode 100644 lib/presentation/home/dialog/confirm_save_order_dialog.dart diff --git a/lib/presentation/home/dialog/confirm_save_order_dialog.dart b/lib/presentation/home/dialog/confirm_save_order_dialog.dart new file mode 100644 index 0000000..5f6bd48 --- /dev/null +++ b/lib/presentation/home/dialog/confirm_save_order_dialog.dart @@ -0,0 +1,410 @@ +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/data/models/response/delivery_response_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart'; +import 'package:enaklo_pos/presentation/home/models/order_type.dart'; +import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; +import 'package:enaklo_pos/presentation/success/pages/success_save_order_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ConfirmSaveOrderDialog extends StatelessWidget { + final String customerName; + final OrderType orderType; + final List items; + final Customer? customer; + final DeliveryModel? delivery; + + const ConfirmSaveOrderDialog({ + super.key, + required this.customerName, + required this.orderType, + required this.items, + this.customer, + this.delivery, + }); + + @override + Widget build(BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 8, + child: Container( + width: MediaQuery.of(context).size.width * 0.9, + constraints: BoxConstraints( + maxWidth: 600, + maxHeight: MediaQuery.of(context).size.height * 0.8, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header + Container( + width: double.infinity, + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8) + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Column( + children: [ + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Icon( + Icons.warning_rounded, + color: Colors.white, + size: 28, + ), + ), + SizedBox(height: 12), + Text( + 'Konfirmasi Pesanan', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Tindakan ini tidak dapat dibatalkan', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 12, + ), + ), + ], + ), + ), + + // Scrollable Content + Flexible( + child: SingleChildScrollView( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Main message + Container( + width: double.infinity, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Text( + message, + style: TextStyle( + fontSize: 14, + height: 1.4, + color: Colors.grey[800], + ), + ), + ), + + if (items.isNotEmpty) ...[ + SizedBox(height: 16), + + // Items section + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Colors.orange[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.orange[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.orange[100], + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + Icons.list_alt_rounded, + color: Colors.orange[700], + size: 16, + ), + ), + SizedBox(width: 8), + Text( + 'Item yang akan dipesan:', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: Colors.orange[800], + ), + ), + ], + ), + ), + Container( + constraints: BoxConstraints(maxHeight: 120), + child: Scrollbar( + child: SingleChildScrollView( + padding: EdgeInsets.only( + left: 16, right: 16, bottom: 16), + child: Column( + children: items.map((item) { + return Container( + margin: EdgeInsets.only(bottom: 6), + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(6), + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.03), + blurRadius: 2, + offset: Offset(0, 1), + ), + ], + ), + child: Row( + children: [ + Container( + width: 6, + height: 6, + decoration: BoxDecoration( + color: Colors.red[400], + shape: BoxShape.circle, + ), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + item.product.name ?? + 'Unknown Product', + style: TextStyle( + fontWeight: + FontWeight.w600, + fontSize: 12, + ), + maxLines: 1, + overflow: + TextOverflow.ellipsis, + ), + Text( + '${item.quantity} qty', + style: TextStyle( + fontSize: 10, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.red[100], + borderRadius: + BorderRadius.circular(4), + ), + child: Text( + ((item.product.price ?? 0) * + item.quantity) + .currencyFormatRpV2, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.red[700], + ), + ), + ), + ], + ), + ); + }).toList(), + ), + ), + ), + ), + ], + ), + ), + ], + + SizedBox(height: 16), + ], + ), + ), + ), + + // Action buttons + Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + ), + child: Row( + children: [ + Expanded( + child: SizedBox( + height: 44, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey[300], + foregroundColor: Colors.grey[700], + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.close_rounded, size: 18), + SizedBox(width: 6), + Text( + 'Batal', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + SizedBox(width: 12), + Expanded( + child: BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: (data) { + context.pushReplacement(SuccessSaveOrderPage( + productQuantity: items, + orderId: data.id ?? "", + )); + }, + error: (message) => + AppFlushbar.showError(context, message), + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => SizedBox( + height: 44, + child: ElevatedButton( + onPressed: () { + context.read().add( + OrderFormEvent.create( + items: items, + customerName: customerName, + orderType: orderType, + table: null, + customer: customer, + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + elevation: 2, + shadowColor: + AppColors.primary.withOpacity(0.3), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.delete_forever_rounded, + size: 18), + SizedBox(width: 6), + Text( + 'Konfirmasi', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + loading: () => + Center(child: CircularProgressIndicator()), + ); + }, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + String get message { + switch (orderType) { + case OrderType.dineIn: + return 'Konfirmasi untuk menyimpan pesanan Dine-In\n\n' + 'Pesanan untuk ${customerName.isNotEmpty ? customerName : "Pelanggan"} akan disimpan ke dalam sistem. ' + 'Pelanggan dapat langsung menikmati pesanan di meja yang telah disediakan. ' + 'Pastikan semua item pesanan sudah sesuai sebelum menyimpan.'; + + case OrderType.delivery: + return 'Konfirmasi untuk menyimpan pesanan Delivery\n\n' + 'Pesanan delivery untuk ${customerName.isNotEmpty ? customerName : "Pelanggan"} akan disimpan. ' + '${delivery != null ? "Pengiriman: ${delivery!.name}. " : ""}' + 'Tim delivery akan segera mempersiapkan pesanan.'; + + case OrderType.takeAway: + return 'Konfirmasi untuk menyimpan pesanan Take Away\n\n' + 'Pesanan take away untuk ${customerName.isNotEmpty ? customerName : "Pelanggan"} akan disimpan. ' + 'Dapur akan mulai mempersiapkan pesanan dan pelanggan dapat mengambil pesanan sesuai estimasi waktu yang diberikan.'; + + case OrderType.freeTable: + return 'Konfirmasi untuk menyimpan pesanan Free Table\n\n' + 'Pesanan free table untuk ${customerName.isNotEmpty ? customerName : "Pelanggan"} akan disimpan. ' + 'Meja akan direservasi dan pesanan akan dipersiapkan sesuai dengan waktu kedatangan pelanggan.'; + } + } +} diff --git a/lib/presentation/home/dialog/save_dialog.dart b/lib/presentation/home/dialog/save_dialog.dart index 60fdf05..ce72923 100644 --- a/lib/presentation/home/dialog/save_dialog.dart +++ b/lib/presentation/home/dialog/save_dialog.dart @@ -3,7 +3,9 @@ import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/data/models/response/delivery_response_model.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart'; +import 'package:enaklo_pos/presentation/home/dialog/confirm_save_order_dialog.dart'; import 'package:enaklo_pos/presentation/home/dialog/payment_add_order_dialog.dart'; import 'package:enaklo_pos/presentation/home/dialog/payment_save_dialog.dart'; import 'package:enaklo_pos/presentation/home/models/order_type.dart'; @@ -16,6 +18,7 @@ class SaveDialog extends StatefulWidget { final OrderType orderType; final List items; final Customer? customer; + final DeliveryModel? deliveryModel; const SaveDialog({ super.key, @@ -24,6 +27,7 @@ class SaveDialog extends StatefulWidget { required this.orderType, required this.items, required this.customer, + this.deliveryModel, }); @override @@ -46,16 +50,29 @@ class _SaveDialogState extends State { subtitle: 'Simpan pesanan dan bayar nanti', onTap: () { context.pop(); - showDialog( - context: context, - builder: (context) => PaymentSaveDialog( - selectedTable: widget.selectedTable, - customerName: widget.customerName, - orderType: widget.orderType, - items: widget.items, - customer: widget.customer, - ), - ); + if (OrderType.dineIn == widget.orderType) { + showDialog( + context: context, + builder: (context) => PaymentSaveDialog( + selectedTable: widget.selectedTable, + customerName: widget.customerName, + orderType: widget.orderType, + items: widget.items, + customer: widget.customer, + ), + ); + } else { + showDialog( + context: context, + builder: (context) => ConfirmSaveOrderDialog( + delivery: widget.deliveryModel, + customerName: widget.customerName, + orderType: widget.orderType, + items: widget.items, + customer: widget.customer, + ), + ); + } }), SpaceHeight(16.0), _item( diff --git a/lib/presentation/home/pages/confirm_payment_page.dart b/lib/presentation/home/pages/confirm_payment_page.dart index 5689c5b..37f6f82 100644 --- a/lib/presentation/home/pages/confirm_payment_page.dart +++ b/lib/presentation/home/pages/confirm_payment_page.dart @@ -5,6 +5,7 @@ import 'package:enaklo_pos/core/components/dashed_divider.dart'; import 'package:enaklo_pos/core/components/flushbar.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/data/models/response/delivery_response_model.dart'; import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart'; import 'package:enaklo_pos/presentation/home/dialog/save_dialog.dart'; import 'package:enaklo_pos/presentation/home/models/order_type.dart'; @@ -925,6 +926,24 @@ class _ConfirmPaymentPageState extends State { orderType, ); + DeliveryModel? delivery = state.maybeWhen( + orElse: () => null, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + deliveryType, + ); + List items = state.maybeWhen( orElse: () => [], loaded: ( @@ -982,6 +1001,7 @@ class _ConfirmPaymentPageState extends State { items: items, orderType: orderType, customer: selectedCustomer, + deliveryModel: delivery, ), ); },