diff --git a/lib/presentation/success/pages/success_payment_page.dart b/lib/presentation/success/pages/success_payment_page.dart index c665448..27bdd75 100644 --- a/lib/presentation/success/pages/success_payment_page.dart +++ b/lib/presentation/success/pages/success_payment_page.dart @@ -1,11 +1,9 @@ -import 'package:enaklo_pos/core/components/buttons.dart'; -import 'package:enaklo_pos/core/components/dashed_divider.dart'; -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/core/extensions/date_time_ext.dart'; import 'package:enaklo_pos/core/extensions/string_ext.dart'; import 'package:enaklo_pos/core/function/app_function.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; import 'package:enaklo_pos/data/models/response/payment_response_model.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; @@ -36,169 +34,48 @@ class _SuccessPaymentPageState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, - body: Center( + body: SafeArea( child: Container( - width: context.deviceWidth * 0.4, - height: context.deviceHeight * 0.8, decoration: BoxDecoration( - color: AppColors.white, - borderRadius: const BorderRadius.all(Radius.circular(12.0)), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColors.primary.withOpacity(0.05), + AppColors.background, + AppColors.background, + ], + ), ), child: BlocBuilder( builder: (context, state) { return state.maybeWhen( - orElse: () => Center( - child: CircularProgressIndicator(), - ), + orElse: () => SizedBox.shrink(), loading: () => Center( child: CircularProgressIndicator(), ), - error: (message) => Center( - child: Text(message), - ), - loadedDetail: (order) => Column( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Text( - 'Pembayaran!', - style: const TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), - ), - Text('Pembayaran berhasil dilalukan', - style: const TextStyle(fontSize: 14)), - ], - ), + loadedDetail: (order) { + return Padding( + padding: const EdgeInsets.all(24.0), + child: Row( + children: [ + // Left Panel - Success Message & Order Info + Expanded( + flex: 35, + child: _buildLeftPanel(order), + ), + + const SizedBox(width: 16), + + // Right Panel - Order Details + Expanded( + flex: 65, + child: _buildRightPanel(order), + ), + ], ), - DashedDivider( - color: AppColors.grey, - ), - SpaceHeight(24), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Icon( - Icons.check_circle_outline, - size: 64, - color: Colors.green, - ), - ), - Spacer(), - Padding( - padding: const EdgeInsets.all(16.0).copyWith(top: 24), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'No. Pesanan', - ), - Text( - order.orderNumber ?? "-", - style: const TextStyle( - fontWeight: FontWeight.bold), - ), - ], - ), - SpaceHeight(4), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Waktu', - ), - Text( - (widget.payment.createdAt ?? DateTime.now()) - .toFormattedDate3(), - style: const TextStyle( - fontWeight: FontWeight.bold), - ), - ], - ), - ], - ), - ), - DashedDivider( - color: AppColors.grey, - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Status Pembayaran', - ), - Text( - 'Lunas', - style: const TextStyle( - fontWeight: FontWeight.bold, - color: Colors.green), - ), - ], - ), - ), - DashedDivider( - color: AppColors.grey, - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Total Pembayaran', - ), - Text( - (widget.payment.amount ?? 0) - .toString() - .currencyFormatRpV2, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), - ), - DashedDivider( - color: AppColors.grey, - ), - Spacer(), - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - Expanded( - child: Button.outlined( - onPressed: () => - context.pushReplacement(DashboardPage()), - label: 'Kembali', - height: 44, - ), - ), - SpaceWidth(12), - Expanded( - child: Button.filled( - onPressed: () async { - onPrint( - context, - productQuantity: widget.productQuantity, - order: order, - ); - }, - label: 'Cetak', - icon: Icon( - Icons.print, - color: AppColors.white, - ), - height: 44, - ), - ), - ], - ), - ), - ], - ), + ); + }, ); }, ), @@ -206,4 +83,888 @@ class _SuccessPaymentPageState extends State { ), ); } + + Widget _buildLeftPanel(Order order) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.1), + blurRadius: 30, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + children: [ + // Success Header + Container( + width: double.infinity, + padding: const EdgeInsets.all(32.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Success Icon + Container( + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: const Icon( + Icons.check_rounded, + size: 48, + color: Colors.white, + ), + ), + + const SizedBox(height: 24), + + // Success Title + const Text( + 'Pesanan Berhasil!', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + + const SizedBox(height: 12), + + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: const Text( + 'Pesanan telah diterima dan sedang diproses', + style: TextStyle( + fontSize: 14, + color: AppColors.primary, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + + // Order Information Section + Expanded( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionTitle('Informasi Pesanan'), + const SizedBox(height: 24), + + // Customer Card + _buildInfoCard( + icon: Icons.person_outline_rounded, + title: 'Nama Pelanggan', + value: order.metadata?['customer_name'] ?? "-", + gradient: [ + Colors.blue.withOpacity(0.1), + Colors.purple.withOpacity(0.1), + ], + ), + + const SizedBox(height: 16), + + // Order Details + Expanded( + child: Column( + children: [ + _buildInfoRow( + icon: Icons.receipt_long_outlined, + label: 'No. Pesanan', + value: order.orderNumber ?? "-", + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.table_restaurant_outlined, + label: 'No. Meja', + value: order.tableNumber ?? "-", + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.access_time_rounded, + label: 'Waktu', + value: (order.createdAt ?? DateTime.now()) + .toFormattedDate3(), + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.check_circle_outline, + label: 'Status Pembayaran', + value: 'Lunas', + valueColor: Colors.green, + showBadge: true, + ), + ], + ), + ), + ], + ), + ), + ), + + // Total and Action Buttons + _buildBottomSection(order), + ], + ), + ); + } + + Widget _buildRightPanel(Order order) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 30, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + children: [ + // Header + Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.background, + Colors.grey.shade50, + ], + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.1), + ], + ), + borderRadius: BorderRadius.circular(16.0), + ), + child: Icon( + Icons.receipt_long_rounded, + color: AppColors.primary, + size: 28, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Detail Pesanan', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + 'Ringkasan item yang dipesan', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Text( + '${widget.productQuantity.length} Items', + style: const TextStyle( + fontSize: 13, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + + // Product List + Expanded( + child: ListView.separated( + padding: const EdgeInsets.all(24.0), + itemCount: widget.productQuantity.length, + separatorBuilder: (context, index) => const SizedBox(height: 12), + itemBuilder: (context, index) { + return _buildProductCard(index); + }, + ), + ), + + // Summary Footer + _buildSummaryFooter(order), + ], + ), + ); + } + + Widget _buildProductCard(int index) { + final item = widget.productQuantity[index]; + final totalPrice = (item.product.price ?? 0) * item.quantity; + + return Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: BorderRadius.circular(16.0), + border: Border.all( + color: Colors.grey.withOpacity(0.1), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // Product Image + Container( + width: 70, + height: 70, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.1), + ], + ), + borderRadius: BorderRadius.circular(16.0), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Icon( + Icons.restaurant_rounded, + color: AppColors.primary, + size: 28, + ), + ), + + const SizedBox(width: 16), + + // Product Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.product.name ?? "-", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + (item.product.price ?? 0).toString().currencyFormatRpV2, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade700, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + + const SizedBox(width: 16), + + // Quantity and Total + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + '${item.quantity}x', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + const SizedBox(height: 8), + Text( + totalPrice.toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildSectionTitle(String title) { + return Text( + title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ); + } + + Widget _buildInfoCard({ + required IconData icon, + required String title, + required String value, + required List gradient, + }) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: gradient, + ), + borderRadius: BorderRadius.circular(16.0), + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + icon, + size: 20, + color: AppColors.primary, + ), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + value, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ); + } + + Widget _buildInfoRow({ + required IconData icon, + required String label, + required String value, + Color? valueColor, + bool showBadge = false, + }) { + return Row( + children: [ + Icon( + icon, + size: 18, + color: Colors.grey.shade600, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + label, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ), + if (showBadge && valueColor != null) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: valueColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.check_circle, + size: 14, + color: valueColor, + ), + const SizedBox(width: 4), + Text( + value, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: valueColor, + ), + ), + ], + ), + ) + else + Text( + value, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: valueColor ?? Colors.black87, + ), + ), + ], + ); + } + + Widget _buildBottomSection(Order order) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Total Amount + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.primary.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Total Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + (order.totalAmount ?? 0).toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ), + + const SizedBox(height: 24), + + // Action Buttons + Row( + children: [ + Expanded( + child: Container( + height: 50, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.primary.withOpacity(0.3), + width: 2, + ), + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(14), + child: InkWell( + borderRadius: BorderRadius.circular(14), + onTap: () { + context.push(DashboardPage()); + }, + child: const Center( + child: Text( + 'Kembali ke Beranda', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ), + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Container( + height: 50, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(16), + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () async { + onPrint( + context, + productQuantity: widget.productQuantity, + order: order, + ); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.print_rounded, + color: Colors.white, + size: 20, + ), + SizedBox(width: 8), + Text( + 'Cetak Struk', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildSummaryFooter(Order order) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Decorative Divider + Container( + height: 1, + margin: const EdgeInsets.only(bottom: 20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + AppColors.primary.withOpacity(0.3), + Colors.transparent, + ], + ), + ), + ), + + // Subtotal Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.shopping_cart_outlined, + size: 16, + color: Colors.grey.shade600, + ), + const SizedBox(width: 8), + Text( + 'Subtotal (${widget.productQuantity.length} items)', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + Text( + (order.totalAmount ?? 0).toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + + const SizedBox(height: 16), + + // Total Payment Row + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.primary.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.payments_rounded, + size: 16, + color: AppColors.primary, + ), + ), + const SizedBox(width: 12), + const Text( + 'Total Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + (order.totalAmount ?? 0).toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } }