From e19d788f477e921ee2dcefac39e310f221fa9576 Mon Sep 17 00:00:00 2001 From: efrilm Date: Fri, 8 Aug 2025 10:29:17 +0700 Subject: [PATCH] feat: update order detail --- .../models/response/order_response_model.dart | 4 + lib/presentation/sales/pages/sales_page.dart | 54 +++--- .../sales/widgets/sales_card.dart | 79 ++++++-- .../sales/widgets/sales_detail.dart | 71 ------- .../widgets/sales_order_information.dart | 146 +++++++++----- .../sales/widgets/sales_payment.dart | 178 +++++++++++------- .../sales/widgets/sales_payment_summary.dart | 94 +++++++++ 7 files changed, 401 insertions(+), 225 deletions(-) delete mode 100644 lib/presentation/sales/widgets/sales_detail.dart create mode 100644 lib/presentation/sales/widgets/sales_payment_summary.dart diff --git a/lib/data/models/response/order_response_model.dart b/lib/data/models/response/order_response_model.dart index e768f1b..3c2d26d 100644 --- a/lib/data/models/response/order_response_model.dart +++ b/lib/data/models/response/order_response_model.dart @@ -129,6 +129,7 @@ class Order { final List? payments; final int? totalPaid; final int? paymentCount; + final String? splitType; Order({ this.id, @@ -156,6 +157,7 @@ class Order { this.payments, this.totalPaid, this.paymentCount, + this.splitType, }); factory Order.fromMap(Map map) { @@ -190,6 +192,7 @@ class Order { : List.from(map['payments'].map((x) => Payment.fromMap(x))), totalPaid: map['total_paid'], paymentCount: map['payment_count'], + splitType: map['split_type'] ?? "", ); } @@ -220,6 +223,7 @@ class Order { 'payments': payments?.map((x) => x.toMap()).toList(), 'total_paid': totalPaid, 'payment_count': paymentCount, + 'split_type': splitType, }; } } diff --git a/lib/presentation/sales/pages/sales_page.dart b/lib/presentation/sales/pages/sales_page.dart index b320fef..5198f4a 100644 --- a/lib/presentation/sales/pages/sales_page.dart +++ b/lib/presentation/sales/pages/sales_page.dart @@ -6,9 +6,9 @@ import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dar import 'package:enaklo_pos/presentation/payment/pages/payment_page.dart'; import 'package:enaklo_pos/presentation/refund/pages/refund_page.dart'; import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/sales/widgets/sales_payment_summary.dart'; import 'package:enaklo_pos/presentation/split_bill/pages/split_bill_page.dart'; import 'package:enaklo_pos/presentation/void/pages/void_page.dart'; -import 'package:enaklo_pos/presentation/sales/widgets/sales_detail.dart'; import 'package:enaklo_pos/presentation/sales/widgets/sales_list_order.dart'; import 'package:enaklo_pos/presentation/sales/widgets/sales_order_information.dart'; import 'package:enaklo_pos/presentation/sales/widgets/sales_payment.dart'; @@ -259,35 +259,35 @@ class _SalesPageState extends State { ), Expanded( child: SingleChildScrollView( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: SalesOrderInformation( - order: orderDetail, - ), - ), - SpaceWidth(16), - Expanded( - child: SalesDetail( - order: orderDetail, - ), - ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Order Header + SalesOrderInformation( + order: orderDetail, + ), + // Order Items + SalesListOrder(order: orderDetail), + const SpaceHeight(16), + + // Payment Summary + SalesPaymentSummary(order: orderDetail), + const SpaceHeight(16), + + // Payment Information + if (orderDetail?.payments != null && + orderDetail?.payments!.isNotEmpty == + true) ...[ + SalesPayment(order: orderDetail), + const SpaceHeight(20), ], - ), - SalesListOrder( - order: orderDetail, - ), - SalesPayment( - order: orderDetail, - ), - ], + ], + ), ), ), - ) + ), ], ), ), diff --git a/lib/presentation/sales/widgets/sales_card.dart b/lib/presentation/sales/widgets/sales_card.dart index aa80a9d..b6b977c 100644 --- a/lib/presentation/sales/widgets/sales_card.dart +++ b/lib/presentation/sales/widgets/sales_card.dart @@ -98,23 +98,7 @@ class SalesCard extends StatelessWidget { ], ), ), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: Colors.green.withOpacity(0.15), - borderRadius: BorderRadius.circular(16), - ), - child: Text( - (order.status ?? "").toUpperCase(), - style: TextStyle( - color: Colors.green, - fontWeight: FontWeight.w600, - fontSize: 12, - letterSpacing: 0.5, - ), - ), - ), + _buildStatus(), ], ), const SizedBox(height: 16), @@ -122,7 +106,10 @@ class SalesCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - (order.totalAmount ?? 0).currencyFormatRpV2, + order.status == 'pending' + ? ((order.totalAmount ?? 0) - (order.totalPaid ?? 0)) + .currencyFormatRpV2 + : (order.totalAmount ?? 0).currencyFormatRpV2, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -142,4 +129,60 @@ class SalesCard extends StatelessWidget { ), ); } + + Widget _buildStatus() { + switch (order.status) { + case 'pending': + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.amber.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + (order.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.amber, + fontWeight: FontWeight.w600, + fontSize: 12, + letterSpacing: 0.5, + ), + ), + ); + case 'completed': + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.green.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + (order.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.green, + fontWeight: FontWeight.w600, + fontSize: 12, + letterSpacing: 0.5, + ), + ), + ); + default: + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + (order.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.grey, + fontWeight: FontWeight.w600, + fontSize: 12, + letterSpacing: 0.5, + ), + ), + ); + } + } } diff --git a/lib/presentation/sales/widgets/sales_detail.dart b/lib/presentation/sales/widgets/sales_detail.dart deleted file mode 100644 index 92afb86..0000000 --- a/lib/presentation/sales/widgets/sales_detail.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:enaklo_pos/core/constants/colors.dart'; -import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; -import 'package:enaklo_pos/data/models/response/order_response_model.dart'; -import 'package:flutter/material.dart'; - -class SalesDetail extends StatelessWidget { - final Order? order; - const SalesDetail({super.key, this.order}); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColors.white, - borderRadius: BorderRadius.circular(8), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Detail', - style: TextStyle( - color: AppColors.black, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - _item( - title: 'Pelanggan', - value: order?.metadata?['customer_name'] ?? "-", - ), - _item( - title: 'Waktu', - value: (order?.createdAt ?? DateTime.now()).toFormattedDate3(), - ), - _item( - title: 'Status', - value: order?.status ?? "-", - ), - ], - ), - ); - } - - Padding _item({ - required String title, - required String value, - }) { - return Padding( - padding: const EdgeInsets.only(top: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - title, - style: const TextStyle( - fontSize: 14, - ), - ), - Text( - value, - style: const TextStyle( - fontSize: 14, - ), - ), - ], - ), - ); - } -} diff --git a/lib/presentation/sales/widgets/sales_order_information.dart b/lib/presentation/sales/widgets/sales_order_information.dart index 6ad054b..61f4437 100644 --- a/lib/presentation/sales/widgets/sales_order_information.dart +++ b/lib/presentation/sales/widgets/sales_order_information.dart @@ -1,3 +1,4 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; import 'package:enaklo_pos/data/models/response/order_response_model.dart'; @@ -10,66 +11,121 @@ class SalesOrderInformation extends StatelessWidget { @override Widget build(BuildContext context) { return Container( + width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: AppColors.white, + color: AppColors.primary, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + order?.orderNumber ?? "", + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + _buildStatus(), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + if (order?.orderType == 'dineIn') ...[ + _buildRowItem(Icons.table_restaurant_outlined, + 'Meja ${order?.tableNumber}'), + const SizedBox(width: 16), + ], + _buildRowItem(Icons.restaurant_outlined, '${order?.orderType}'), + ], + ), + const SizedBox(height: 8), Text( - 'Informasi Pesanan', - style: TextStyle( - color: AppColors.black, - fontSize: 16, - fontWeight: FontWeight.w600, - ), + 'Pelanggan: ${order?.metadata?['customer_name'] ?? ""}', + style: const TextStyle(color: Colors.white, fontSize: 14), ), - _item( - title: 'No. Order', - value: "${order?.orderNumber}", - ), - _item( - title: 'Tanggal', - value: (order?.createdAt ?? DateTime.now()).toFormattedDate2(), - ), - _item( - title: 'No. Meja', - value: order?.tableNumber ?? "-", - ), - _item( - title: 'Jenis Order', - value: order?.orderType ?? "-", + Text( + 'Dibuat: ${order?.createdAt?.toFormattedDate3() ?? ""}', + style: const TextStyle(color: Colors.white70, fontSize: 12), ), ], ), ); } - Padding _item({ - required String title, - required String value, - }) { - return Padding( - padding: const EdgeInsets.only(top: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - title, - style: const TextStyle( - fontSize: 14, - ), - ), - Text( - value, - style: const TextStyle( - fontSize: 14, - ), - ), - ], - ), + Row _buildRowItem(IconData icon, String title) { + return Row( + children: [ + Icon( + icon, + color: Colors.white70, + size: 16, + ), + const SpaceWidth(4), + Text( + title, + style: const TextStyle(color: Colors.white70, fontSize: 14), + ), + ], ); } + + Container _buildStatus() { + switch (order?.status) { + case 'pending': + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.orange, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + (order?.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ); + case 'completed': + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.greenAccent, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + (order?.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ); + default: + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + (order?.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ); + } + } } diff --git a/lib/presentation/sales/widgets/sales_payment.dart b/lib/presentation/sales/widgets/sales_payment.dart index 8ef811a..3d96463 100644 --- a/lib/presentation/sales/widgets/sales_payment.dart +++ b/lib/presentation/sales/widgets/sales_payment.dart @@ -1,4 +1,3 @@ -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/int_ext.dart'; @@ -12,88 +11,139 @@ class SalesPayment extends StatelessWidget { @override Widget build(BuildContext context) { return Container( + width: double.infinity, padding: const EdgeInsets.all(16), - margin: const EdgeInsets.only(top: 16), decoration: BoxDecoration( - color: AppColors.white, + color: Colors.white, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Informasi Pesanan', - style: TextStyle( - color: AppColors.black, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - SpaceHeight(12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Subtotal ${order?.orderItems?.length} Produk', - style: const TextStyle( + 'Informasi Pembayaran', + style: TextStyle( fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - Text( - (order?.subtotal)?.currencyFormatRp ?? "0", - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - SpaceHeight(12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Pajak', - style: const TextStyle( - fontSize: 16, - ), - ), - Text( - (order?.taxAmount)?.currencyFormatRp ?? "0", - style: const TextStyle( - fontSize: 16, - ), - ), - ], - ), - SpaceHeight(12), - DashedDivider( - color: AppColors.stroke, - ), - SpaceHeight(12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Total', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w700, - ), - ), - Text( - (order?.totalAmount)?.currencyFormatRp ?? "0", - style: const TextStyle( + fontWeight: FontWeight.bold, color: AppColors.primary, - fontSize: 18, - fontWeight: FontWeight.w700, + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.amber.shade100, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + order?.paymentStatus ?? "", + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.amber.shade800, + ), ), ), ], ), + const SpaceHeight(12), + ...List.generate( + order?.payments?.length ?? 0, + (index) => _buildPaymentItem(order?.payments?[index] ?? Payment()), + ), + const SpaceHeight(12), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Jumlah yang Dibayar', + style: TextStyle(color: Colors.grey.shade700), + ), + Text( + (order?.totalPaid ?? 0).currencyFormatRpV2, + style: TextStyle(fontWeight: FontWeight.w500), + ), + ], + ), + if (((order?.totalAmount ?? 0) - (order?.totalPaid ?? 0)) != 0) ...[ + const SpaceHeight(4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Sisa Tagihan', + style: TextStyle( + color: Colors.red.shade700, + fontWeight: FontWeight.w500, + ), + ), + Text( + ((order?.totalAmount ?? 0) - (order?.totalPaid ?? 0)) + .currencyFormatRpV2, + style: TextStyle( + color: Colors.red.shade700, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], ], ), ); } + + Row _buildPaymentItem(Payment payment) { + return Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: Colors.green.shade100, + borderRadius: BorderRadius.circular(4), + ), + child: Icon( + Icons.payments, + color: Colors.green.shade700, + size: 16, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + payment.paymentMethodName ?? "", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + if ((payment.splitTotal ?? 0) > 1) + Text( + 'Split ${payment.splitNumber ?? 0} of ${payment.splitTotal ?? 0}', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + Text( + (payment.amount ?? 0).currencyFormatRpV2, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.green, + ), + ), + ], + ); + } } diff --git a/lib/presentation/sales/widgets/sales_payment_summary.dart b/lib/presentation/sales/widgets/sales_payment_summary.dart new file mode 100644 index 0000000..698f51d --- /dev/null +++ b/lib/presentation/sales/widgets/sales_payment_summary.dart @@ -0,0 +1,94 @@ +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/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter/material.dart'; + +class SalesPaymentSummary extends StatelessWidget { + final Order? order; + const SalesPaymentSummary({super.key, this.order}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Ringkasan Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + const SpaceHeight(12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Subtotal', + style: TextStyle(color: Colors.grey.shade700), + ), + Text((order?.subtotal ?? 0).currencyFormatRpV2), + ], + ), + const SpaceHeight(4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Tax', + style: TextStyle(color: Colors.grey.shade700), + ), + Text((order?.taxAmount ?? 0).currencyFormatRpV2), + ], + ), + const SpaceHeight(4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Discount', + style: TextStyle(color: Colors.grey.shade700), + ), + Text((order?.discountAmount ?? 0).currencyFormatRpV2), + ], + ), + const SpaceHeight(8), + const DashedDivider( + color: AppColors.grey, + ), + const SpaceHeight(8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Total', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Text( + (order?.totalAmount ?? 0).currencyFormatRpV2, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ], + ), + ); + } +}