From b4d2d859eb549513e3e48f827b11b8561996a7a5 Mon Sep 17 00:00:00 2001 From: efrilm Date: Mon, 18 Aug 2025 15:30:34 +0700 Subject: [PATCH] feat: order detail --- .../order/order_detail/order_detail_page.dart | 1228 +++++++++++++++++ .../pages/order/order_list/order_page.dart | 7 +- lib/presentation/router/app_router.dart | 3 + lib/presentation/router/app_router.gr.dart | 218 +-- 4 files changed, 1366 insertions(+), 90 deletions(-) create mode 100644 lib/presentation/pages/order/order_detail/order_detail_page.dart diff --git a/lib/presentation/pages/order/order_detail/order_detail_page.dart b/lib/presentation/pages/order/order_detail/order_detail_page.dart new file mode 100644 index 0000000..b13c89f --- /dev/null +++ b/lib/presentation/pages/order/order_detail/order_detail_page.dart @@ -0,0 +1,1228 @@ +import 'package:flutter/material.dart'; +import 'package:auto_route/auto_route.dart'; +import 'dart:math' show cos, sin; + +import '../../../../common/theme/theme.dart'; +import '../../../../domain/order/order.dart'; +import '../../../components/appbar/appbar.dart'; + +@RoutePage() +class OrderDetailPage extends StatelessWidget { + final Order order; + + const OrderDetailPage({super.key, required this.order}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + body: CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: 120, + pinned: true, + backgroundColor: AppColor.primary, + flexibleSpace: const CustomAppBar(title: "Detail Pesanan"), + elevation: 0, + ), + SliverToBoxAdapter( + child: Column( + children: [ + // Order Status Card + _buildOrderStatusCard(), + const SizedBox(height: 16), + + // Table Visual Card (for dine-in) + if (_isDineIn()) _buildTableVisualCard(), + + // Order Info Card + _buildOrderInfoCard(), + const SizedBox(height: 16), + + // Order Items Section + _buildOrderItemsSection(), + const SizedBox(height: 16), + + // Payment Summary Card + _buildPaymentSummaryCard(), + const SizedBox(height: 16), + + // Payment Methods Section + _buildPaymentMethodsSection(), + const SizedBox(height: 24), + ], + ), + ), + ], + ), + ); + } + + Widget _buildOrderStatusCard() { + Color statusColor = _getStatusColor(order.status); + return Container( + margin: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + statusColor.withOpacity(0.15), + statusColor.withOpacity(0.08), + Colors.white, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + stops: const [0.0, 0.5, 1.0], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: statusColor.withOpacity(0.3), width: 1.5), + boxShadow: [ + BoxShadow( + color: statusColor.withOpacity(0.2), + blurRadius: 15, + offset: const Offset(0, 5), + ), + BoxShadow( + color: Colors.white, + blurRadius: 8, + offset: const Offset(-2, -2), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [statusColor, statusColor.withOpacity(0.8)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: statusColor.withOpacity(0.4), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + ), + child: Icon( + _getStatusIcon(order.status), + color: Colors.white, + size: 28, + ), + ), + const SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Order Number', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + Text( + order.orderNumber, + style: AppStyle.xl.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 20), + Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.7), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: statusColor.withOpacity(0.2)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Status Pesanan', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [statusColor, statusColor.withOpacity(0.8)], + ), + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: statusColor.withOpacity(0.3), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + order.status.toUpperCase(), + style: AppStyle.sm.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + letterSpacing: 0.5, + ), + ), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + 'Total Amount', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + Text( + 'Rp ${_formatCurrency(order.totalAmount)}', + style: AppStyle.xxl.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildTableVisualCard() { + return Container( + margin: const EdgeInsets.fromLTRB(16, 0, 16, 16), + height: 220, // Increased height + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.primary, + AppColor.primaryLight, + AppColor.primary.withOpacity(0.8), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + stops: const [0.0, 0.5, 1.0], + ), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: ClipRRect( + // Prevent overflow + borderRadius: BorderRadius.circular(20), + child: Stack( + children: [ + // Background Pattern + Positioned.fill(child: CustomPaint(painter: TablePatternPainter())), + + // Content with proper spacing + Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + // Header + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(15), + border: Border.all( + color: AppColor.white.withOpacity(0.3), + width: 1, + ), + ), + child: const Icon( + Icons.restaurant, + color: AppColor.white, + size: 24, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Makan di Tempat', + style: AppStyle.sm.copyWith( + color: AppColor.white.withOpacity(0.9), + fontWeight: FontWeight.w500, + ), + ), + Text( + 'Dine In Experience', + style: AppStyle.lg.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + + const SizedBox(height: 20), // Fixed spacing + // Table Number Visual + Expanded( + child: Center( + child: Container( + width: 90, + height: 90, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + AppColor.white, + AppColor.white.withOpacity(0.9), + AppColor.white.withOpacity(0.7), + ], + stops: const [0.0, 0.7, 1.0], + ), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.2), + blurRadius: 15, + offset: const Offset(0, 5), + ), + ], + ), + child: Stack( + children: [ + // Table surface + Center( + child: Container( + width: 70, + height: 70, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.white, + border: Border.all( + color: AppColor.primary.withOpacity(0.3), + width: 2, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'MEJA', + style: AppStyle.xs.copyWith( + color: AppColor.primary.withOpacity( + 0.7, + ), + fontWeight: FontWeight.w600, + letterSpacing: 1, + ), + ), + Text( + order.tableNumber, + style: AppStyle.xl.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + // Table legs (decorative) + ...List.generate(4, (index) { + final angle = (index * 90.0) * (3.14159 / 180); + return Positioned( + left: 45 + 30 * cos(angle) - 2, + top: 45 + 30 * sin(angle) - 8, + child: Container( + width: 4, + height: 16, + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.3), + borderRadius: BorderRadius.circular(2), + ), + ), + ); + }), + ], + ), + ), + ), + ), + + const SizedBox(height: 16), // Fixed spacing + // Bottom Info with flexible layout + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppColor.white.withOpacity(0.3), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.people, + size: 16, + color: AppColor.white.withOpacity(0.9), + ), + const SizedBox(width: 6), + Text( + '${order.orderItems.length} Items', + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: AppColor.success.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppColor.success.withOpacity(0.3), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.schedule, + size: 16, + color: AppColor.white.withOpacity(0.9), + ), + const SizedBox(width: 6), + Text( + _formatDateTime(order.createdAt), + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildOrderInfoCard() { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Colors.white, Color(0xFFFAFBFF)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColor.primary.withOpacity(0.1)), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.08), + blurRadius: 20, + offset: const Offset(0, 5), + ), + const BoxShadow( + color: Colors.white, + blurRadius: 6, + offset: Offset(-3, -3), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: AppColor.primaryGradient, + ), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.info_outline, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(width: 16), + Text( + 'Informasi Pesanan', + style: AppStyle.xl.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + ], + ), + const SizedBox(height: 24), + if (_shouldShowTableNumber()) + _buildInfoRow(Icons.table_restaurant, 'Meja', order.tableNumber), + _buildInfoRow( + Icons.delivery_dining, + 'Tipe Pesanan', + order.orderType, + ), + _buildInfoRow( + Icons.payment, + 'Status Pembayaran', + order.paymentStatus, + ), + _buildInfoRow( + Icons.access_time, + 'Dibuat', + _formatDateTime(order.createdAt), + ), + if (order.notes.isNotEmpty) + _buildInfoRow(Icons.note, 'Catatan', order.notes), + ], + ), + ), + ); + } + + Widget _buildInfoRow(IconData icon, String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, size: 16, color: AppColor.primary), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + const SizedBox(height: 2), + Text( + value, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildOrderItemsSection() { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(20, 20, 20, 0), + child: Row( + children: [ + Text( + 'Item Pesanan', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${order.orderItems.length} Item', + style: AppStyle.sm.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(20), + itemCount: order.orderItems.length, + separatorBuilder: (context, index) => const Divider(height: 24), + itemBuilder: (context, index) { + final item = order.orderItems[index]; + return _buildOrderItemCard(item); + }, + ), + ], + ), + ); + } + + Widget _buildOrderItemCard(OrderItem item) { + // Assume we add status to OrderItem model or determine from order status + String itemStatus = _getItemStatus(item); + Color itemStatusColor = _getItemStatusColor(itemStatus); + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + itemStatusColor.withOpacity(0.05), + itemStatusColor.withOpacity(0.02), + Colors.white, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: itemStatusColor.withOpacity(0.3)), + boxShadow: [ + BoxShadow( + color: itemStatusColor.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + Row( + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [itemStatusColor, itemStatusColor.withOpacity(0.8)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: itemStatusColor.withOpacity(0.3), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: const Icon( + Icons.fastfood, + color: Colors.white, + size: 28, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + item.productName, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: itemStatusColor.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + itemStatus, + style: AppStyle.xs.copyWith( + color: itemStatusColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'Qty: ${item.quantity}', + style: AppStyle.xs.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(width: 8), + Text( + 'Rp ${_formatCurrency(item.price)} each', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + if (item.discountAmount > 0) ...[ + const SizedBox(height: 4), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: AppColor.error.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + 'Diskon: -Rp ${_formatCurrency(item.discountAmount)}', + style: AppStyle.xs.copyWith( + color: AppColor.error, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ], + ), + ), + ], + ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.7), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: itemStatusColor.withOpacity(0.2)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Item', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + 'Rp ${_formatCurrency(item.total)}', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + if (item.discountAmount > 0) + Text( + 'Rp ${_formatCurrency(item.subtotal)}', + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + decoration: TextDecoration.lineThrough, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildPaymentSummaryCard() { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.primary.withOpacity(0.05), + AppColor.primary.withOpacity(0.02), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColor.primary.withOpacity(0.2)), + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Ringkasan Pembayaran', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + const SizedBox(height: 16), + _buildSummaryRow('Subtotal', order.subtotal), + if (order.discountAmount > 0) + _buildSummaryRow( + 'Diskon', + -order.discountAmount, + isDiscount: true, + ), + _buildSummaryRow('Pajak', order.taxAmount), + const Divider(height: 24), + _buildSummaryRow('Total', order.totalAmount, isTotal: true), + _buildSummaryRow('Dibayar', order.totalPaid, isSuccess: true), + if (order.remainingAmount > 0) + _buildSummaryRow('Sisa', order.remainingAmount, isError: true), + ], + ), + ), + ); + } + + Widget _buildSummaryRow( + String label, + int amount, { + bool isTotal = false, + bool isDiscount = false, + bool isSuccess = false, + bool isError = false, + }) { + Color textColor = AppColor.textPrimary; + if (isTotal) textColor = AppColor.primary; + if (isDiscount) textColor = AppColor.error; + if (isSuccess) textColor = AppColor.success; + if (isError) textColor = AppColor.error; + + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: AppStyle.md.copyWith( + color: isTotal ? AppColor.primary : AppColor.textSecondary, + fontWeight: isTotal ? FontWeight.bold : FontWeight.normal, + ), + ), + Text( + '${amount < 0 ? '-' : ''}Rp ${_formatCurrency(amount.abs())}', + style: AppStyle.md.copyWith( + color: textColor, + fontWeight: isTotal ? FontWeight.bold : FontWeight.w600, + ), + ), + ], + ), + ); + } + + Widget _buildPaymentMethodsSection() { + if (order.payments.isEmpty) return const SizedBox(); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(20, 20, 20, 0), + child: Text( + 'Metode Pembayaran', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + ), + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(20), + itemCount: order.payments.length, + separatorBuilder: (context, index) => const SizedBox(height: 12), + itemBuilder: (context, index) { + final payment = order.payments[index]; + return _buildPaymentMethodCard(payment); + }, + ), + ], + ), + ); + } + + Widget _buildPaymentMethodCard(OrderPayment payment) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.success.withOpacity(0.05), + AppColor.success.withOpacity(0.02), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.success.withOpacity(0.3)), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.success.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + _getPaymentIcon(payment.paymentMethodType), + color: AppColor.success, + size: 20, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + payment.paymentMethodName, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 4), + Text( + payment.paymentMethodType, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + if (payment.splitType.isNotEmpty && payment.splitTotal > 1) ...[ + const SizedBox(height: 4), + Text( + 'Split ${payment.splitNumber}/${payment.splitTotal}', + style: AppStyle.xs.copyWith(color: AppColor.info), + ), + ], + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + 'Rp ${_formatCurrency(payment.amount)}', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.success, + ), + ), + Container( + margin: const EdgeInsets.only(top: 4), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: _getPaymentStatusColor( + payment.status, + ).withOpacity(0.2), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + payment.status, + style: AppStyle.xs.copyWith( + color: _getPaymentStatusColor(payment.status), + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ], + ), + ); + } + + bool _isDineIn() { + final orderType = order.orderType + .toLowerCase() + .replaceAll('-', '') + .replaceAll('_', ''); + return orderType == 'dinein' || + orderType == 'dine' || + orderType.contains('dinein'); + } + + bool _shouldShowTableNumber() { + final orderType = order.orderType + .toLowerCase() + .replaceAll('-', '') + .replaceAll('_', '') + .replaceAll(' ', ''); + + // Don't show table number for these order types + final noTableTypes = [ + 'takeaway', + 'takeout', + 'pickup', + 'delivery', + 'dinein', + ]; + + return !noTableTypes.any((type) => orderType.contains(type)) && + order.tableNumber.isNotEmpty; + } + + String _formatCurrency(int amount) { + return amount.toString().replaceAllMapped( + RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]}.', + ); + } + + String _formatDateTime(String dateTime) { + // Simple date formatting - you might want to use intl package for better formatting + return dateTime.split('T')[0]; + } + + Color _getStatusColor(String status) { + switch (status.toLowerCase()) { + case 'completed': + return AppColor.success; + case 'pending': + return AppColor.warning; + case 'cancelled': + return AppColor.error; + default: + return AppColor.info; + } + } + + IconData _getStatusIcon(String status) { + switch (status.toLowerCase()) { + case 'completed': + return Icons.check_circle; + case 'pending': + return Icons.access_time; + case 'cancelled': + return Icons.cancel; + default: + return Icons.info; + } + } + + IconData _getPaymentIcon(String type) { + switch (type.toLowerCase()) { + case 'cash': + return Icons.money; + case 'card': + return Icons.credit_card; + case 'digital': + return Icons.qr_code; + default: + return Icons.payment; + } + } + + Color _getPaymentStatusColor(String status) { + switch (status.toLowerCase()) { + case 'success': + return AppColor.success; + case 'pending': + return AppColor.warning; + case 'failed': + return AppColor.error; + default: + return AppColor.info; + } + } + + String _getItemStatus(OrderItem item) { + // Logic untuk menentukan status item berdasarkan: + // 1. Status order secara keseluruhan + // 2. Jika ada field status di OrderItem model + // 3. Waktu pembuatan vs waktu sekarang + + switch (order.status.toLowerCase()) { + case 'completed': + return 'Selesai'; + case 'pending': + return 'Menunggu'; + case 'cancelled': + return 'Dibatalkan'; + default: + return 'Menunggu'; + } + } + + Color _getItemStatusColor(String status) { + switch (status.toLowerCase()) { + case 'selesai': + return AppColor.success; + case 'diproses': + return AppColor.warning; + case 'dibatalkan': + return AppColor.error; + case 'siap': + return AppColor.info; + default: + return AppColor.primary; + } + } +} + +class TablePatternPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = AppColor.white.withOpacity(0.1) + ..strokeWidth = 1 + ..style = PaintingStyle.stroke; + + // Draw decorative pattern + for (int i = 0; i < 8; i++) { + final offset = i * 20.0; + canvas.drawLine( + Offset(offset, 0), + Offset(offset + size.height * 0.3, size.height), + paint, + ); + } + + // Draw subtle dots pattern + final dotPaint = Paint() + ..color = AppColor.white.withOpacity(0.05) + ..style = PaintingStyle.fill; + + for (int x = 0; x < size.width; x += 30) { + for (int y = 0; y < size.height; y += 30) { + canvas.drawCircle(Offset(x.toDouble(), y.toDouble()), 2, dotPaint); + } + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/presentation/pages/order/order_list/order_page.dart b/lib/presentation/pages/order/order_list/order_page.dart index 7e22051..341e662 100644 --- a/lib/presentation/pages/order/order_list/order_page.dart +++ b/lib/presentation/pages/order/order_list/order_page.dart @@ -10,6 +10,7 @@ import '../../../components/appbar/appbar.dart'; import '../../../components/button/button.dart'; import '../../../components/spacer/spacer.dart'; import '../../../components/widgets/empty_widget.dart'; +import '../../../router/app_router.gr.dart'; import 'widgets/status_tile.dart'; import 'widgets/order_tile.dart'; @@ -219,7 +220,11 @@ class _OrderPageState extends State with TickerProviderStateMixin { padding: EdgeInsets.zero, itemBuilder: (context, index) { return OrderTile( - onTap: () {}, + onTap: () => context.router.push( + OrderDetailRoute( + order: state.orders[index], + ), + ), order: state.orders[index], ); }, diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index 8a0c267..5096fcf 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -51,5 +51,8 @@ class AppRouter extends RootStackRouter { // Error AutoRoute(page: ErrorRoute.page), + + // Order + AutoRoute(page: OrderDetailRoute.page), ]; } diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index 2101015..4019650 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -9,6 +9,7 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:apskel_owner_flutter/domain/order/order.dart' as _i21; import 'package:apskel_owner_flutter/presentation/pages/auth/login/login_page.dart' as _i8; import 'package:apskel_owner_flutter/presentation/pages/customer/customer_page.dart' @@ -27,50 +28,52 @@ import 'package:apskel_owner_flutter/presentation/pages/language/language_page.d as _i7; import 'package:apskel_owner_flutter/presentation/pages/main/main_page.dart' as _i9; -import 'package:apskel_owner_flutter/presentation/pages/order/order_list/order_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/order/order_detail/order_detail_page.dart' as _i10; -import 'package:apskel_owner_flutter/presentation/pages/product/product_list/product_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/order/order_list/order_page.dart' as _i11; -import 'package:apskel_owner_flutter/presentation/pages/profile/profile_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/product/product_list/product_page.dart' as _i12; -import 'package:apskel_owner_flutter/presentation/pages/purchase/purchase_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/profile/profile_page.dart' as _i13; -import 'package:apskel_owner_flutter/presentation/pages/report/report_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/purchase/purchase_page.dart' as _i14; -import 'package:apskel_owner_flutter/presentation/pages/sales/sales_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/report/report_page.dart' as _i15; -import 'package:apskel_owner_flutter/presentation/pages/schedule/schedule_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/sales/sales_page.dart' as _i16; -import 'package:apskel_owner_flutter/presentation/pages/splash/splash_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/schedule/schedule_page.dart' as _i17; -import 'package:auto_route/auto_route.dart' as _i18; -import 'package:flutter/material.dart' as _i19; +import 'package:apskel_owner_flutter/presentation/pages/splash/splash_page.dart' + as _i18; +import 'package:auto_route/auto_route.dart' as _i19; +import 'package:flutter/material.dart' as _i20; /// generated route for /// [_i1.CustomerPage] -class CustomerRoute extends _i18.PageRouteInfo { - const CustomerRoute({List<_i18.PageRouteInfo>? children}) +class CustomerRoute extends _i19.PageRouteInfo { + const CustomerRoute({List<_i19.PageRouteInfo>? children}) : super(CustomerRoute.name, initialChildren: children); static const String name = 'CustomerRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return _i18.WrappedRoute(child: const _i1.CustomerPage()); + return _i19.WrappedRoute(child: const _i1.CustomerPage()); }, ); } /// generated route for /// [_i2.DailyTasksFormPage] -class DailyTasksFormRoute extends _i18.PageRouteInfo { - const DailyTasksFormRoute({List<_i18.PageRouteInfo>? children}) +class DailyTasksFormRoute extends _i19.PageRouteInfo { + const DailyTasksFormRoute({List<_i19.PageRouteInfo>? children}) : super(DailyTasksFormRoute.name, initialChildren: children); static const String name = 'DailyTasksFormRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { return const _i2.DailyTasksFormPage(); @@ -80,16 +83,16 @@ class DailyTasksFormRoute extends _i18.PageRouteInfo { /// generated route for /// [_i3.ErrorPage] -class ErrorRoute extends _i18.PageRouteInfo { +class ErrorRoute extends _i19.PageRouteInfo { ErrorRoute({ - _i19.Key? key, + _i20.Key? key, String? title, String? message, - _i19.VoidCallback? onRetry, - _i19.VoidCallback? onBack, + _i20.VoidCallback? onRetry, + _i20.VoidCallback? onBack, String? errorCode, - _i19.IconData? errorIcon, - List<_i18.PageRouteInfo>? children, + _i20.IconData? errorIcon, + List<_i19.PageRouteInfo>? children, }) : super( ErrorRoute.name, args: ErrorRouteArgs( @@ -106,7 +109,7 @@ class ErrorRoute extends _i18.PageRouteInfo { static const String name = 'ErrorRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { final args = data.argsAs( @@ -136,19 +139,19 @@ class ErrorRouteArgs { this.errorIcon, }); - final _i19.Key? key; + final _i20.Key? key; final String? title; final String? message; - final _i19.VoidCallback? onRetry; + final _i20.VoidCallback? onRetry; - final _i19.VoidCallback? onBack; + final _i20.VoidCallback? onBack; final String? errorCode; - final _i19.IconData? errorIcon; + final _i20.IconData? errorIcon; @override String toString() { @@ -158,29 +161,29 @@ class ErrorRouteArgs { /// generated route for /// [_i4.FinancePage] -class FinanceRoute extends _i18.PageRouteInfo { - const FinanceRoute({List<_i18.PageRouteInfo>? children}) +class FinanceRoute extends _i19.PageRouteInfo { + const FinanceRoute({List<_i19.PageRouteInfo>? children}) : super(FinanceRoute.name, initialChildren: children); static const String name = 'FinanceRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return _i18.WrappedRoute(child: const _i4.FinancePage()); + return _i19.WrappedRoute(child: const _i4.FinancePage()); }, ); } /// generated route for /// [_i5.HomePage] -class HomeRoute extends _i18.PageRouteInfo { - const HomeRoute({List<_i18.PageRouteInfo>? children}) +class HomeRoute extends _i19.PageRouteInfo { + const HomeRoute({List<_i19.PageRouteInfo>? children}) : super(HomeRoute.name, initialChildren: children); static const String name = 'HomeRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { return const _i5.HomePage(); @@ -190,29 +193,29 @@ class HomeRoute extends _i18.PageRouteInfo { /// generated route for /// [_i6.InventoryPage] -class InventoryRoute extends _i18.PageRouteInfo { - const InventoryRoute({List<_i18.PageRouteInfo>? children}) +class InventoryRoute extends _i19.PageRouteInfo { + const InventoryRoute({List<_i19.PageRouteInfo>? children}) : super(InventoryRoute.name, initialChildren: children); static const String name = 'InventoryRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return _i18.WrappedRoute(child: const _i6.InventoryPage()); + return _i19.WrappedRoute(child: const _i6.InventoryPage()); }, ); } /// generated route for /// [_i7.LanguagePage] -class LanguageRoute extends _i18.PageRouteInfo { - const LanguageRoute({List<_i18.PageRouteInfo>? children}) +class LanguageRoute extends _i19.PageRouteInfo { + const LanguageRoute({List<_i19.PageRouteInfo>? children}) : super(LanguageRoute.name, initialChildren: children); static const String name = 'LanguageRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { return const _i7.LanguagePage(); @@ -222,29 +225,29 @@ class LanguageRoute extends _i18.PageRouteInfo { /// generated route for /// [_i8.LoginPage] -class LoginRoute extends _i18.PageRouteInfo { - const LoginRoute({List<_i18.PageRouteInfo>? children}) +class LoginRoute extends _i19.PageRouteInfo { + const LoginRoute({List<_i19.PageRouteInfo>? children}) : super(LoginRoute.name, initialChildren: children); static const String name = 'LoginRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return _i18.WrappedRoute(child: const _i8.LoginPage()); + return _i19.WrappedRoute(child: const _i8.LoginPage()); }, ); } /// generated route for /// [_i9.MainPage] -class MainRoute extends _i18.PageRouteInfo { - const MainRoute({List<_i18.PageRouteInfo>? children}) +class MainRoute extends _i19.PageRouteInfo { + const MainRoute({List<_i19.PageRouteInfo>? children}) : super(MainRoute.name, initialChildren: children); static const String name = 'MainRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { return const _i9.MainPage(); @@ -253,129 +256,166 @@ class MainRoute extends _i18.PageRouteInfo { } /// generated route for -/// [_i10.OrderPage] -class OrderRoute extends _i18.PageRouteInfo { - const OrderRoute({List<_i18.PageRouteInfo>? children}) +/// [_i10.OrderDetailPage] +class OrderDetailRoute extends _i19.PageRouteInfo { + OrderDetailRoute({ + _i20.Key? key, + required _i21.Order order, + List<_i19.PageRouteInfo>? children, + }) : super( + OrderDetailRoute.name, + args: OrderDetailRouteArgs(key: key, order: order), + initialChildren: children, + ); + + static const String name = 'OrderDetailRoute'; + + static _i19.PageInfo page = _i19.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i10.OrderDetailPage(key: args.key, order: args.order); + }, + ); +} + +class OrderDetailRouteArgs { + const OrderDetailRouteArgs({this.key, required this.order}); + + final _i20.Key? key; + + final _i21.Order order; + + @override + String toString() { + return 'OrderDetailRouteArgs{key: $key, order: $order}'; + } +} + +/// generated route for +/// [_i11.OrderPage] +class OrderRoute extends _i19.PageRouteInfo { + const OrderRoute({List<_i19.PageRouteInfo>? children}) : super(OrderRoute.name, initialChildren: children); static const String name = 'OrderRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return _i18.WrappedRoute(child: const _i10.OrderPage()); + return _i19.WrappedRoute(child: const _i11.OrderPage()); }, ); } /// generated route for -/// [_i11.ProductPage] -class ProductRoute extends _i18.PageRouteInfo { - const ProductRoute({List<_i18.PageRouteInfo>? children}) +/// [_i12.ProductPage] +class ProductRoute extends _i19.PageRouteInfo { + const ProductRoute({List<_i19.PageRouteInfo>? children}) : super(ProductRoute.name, initialChildren: children); static const String name = 'ProductRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return _i18.WrappedRoute(child: const _i11.ProductPage()); + return _i19.WrappedRoute(child: const _i12.ProductPage()); }, ); } /// generated route for -/// [_i12.ProfilePage] -class ProfileRoute extends _i18.PageRouteInfo { - const ProfileRoute({List<_i18.PageRouteInfo>? children}) +/// [_i13.ProfilePage] +class ProfileRoute extends _i19.PageRouteInfo { + const ProfileRoute({List<_i19.PageRouteInfo>? children}) : super(ProfileRoute.name, initialChildren: children); static const String name = 'ProfileRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return _i18.WrappedRoute(child: const _i12.ProfilePage()); + return _i19.WrappedRoute(child: const _i13.ProfilePage()); }, ); } /// generated route for -/// [_i13.PurchasePage] -class PurchaseRoute extends _i18.PageRouteInfo { - const PurchaseRoute({List<_i18.PageRouteInfo>? children}) +/// [_i14.PurchasePage] +class PurchaseRoute extends _i19.PageRouteInfo { + const PurchaseRoute({List<_i19.PageRouteInfo>? children}) : super(PurchaseRoute.name, initialChildren: children); static const String name = 'PurchaseRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return const _i13.PurchasePage(); + return const _i14.PurchasePage(); }, ); } /// generated route for -/// [_i14.ReportPage] -class ReportRoute extends _i18.PageRouteInfo { - const ReportRoute({List<_i18.PageRouteInfo>? children}) +/// [_i15.ReportPage] +class ReportRoute extends _i19.PageRouteInfo { + const ReportRoute({List<_i19.PageRouteInfo>? children}) : super(ReportRoute.name, initialChildren: children); static const String name = 'ReportRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return _i18.WrappedRoute(child: const _i14.ReportPage()); + return _i19.WrappedRoute(child: const _i15.ReportPage()); }, ); } /// generated route for -/// [_i15.SalesPage] -class SalesRoute extends _i18.PageRouteInfo { - const SalesRoute({List<_i18.PageRouteInfo>? children}) +/// [_i16.SalesPage] +class SalesRoute extends _i19.PageRouteInfo { + const SalesRoute({List<_i19.PageRouteInfo>? children}) : super(SalesRoute.name, initialChildren: children); static const String name = 'SalesRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return _i18.WrappedRoute(child: const _i15.SalesPage()); + return _i19.WrappedRoute(child: const _i16.SalesPage()); }, ); } /// generated route for -/// [_i16.SchedulePage] -class ScheduleRoute extends _i18.PageRouteInfo { - const ScheduleRoute({List<_i18.PageRouteInfo>? children}) +/// [_i17.SchedulePage] +class ScheduleRoute extends _i19.PageRouteInfo { + const ScheduleRoute({List<_i19.PageRouteInfo>? children}) : super(ScheduleRoute.name, initialChildren: children); static const String name = 'ScheduleRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return const _i16.SchedulePage(); + return const _i17.SchedulePage(); }, ); } /// generated route for -/// [_i17.SplashPage] -class SplashRoute extends _i18.PageRouteInfo { - const SplashRoute({List<_i18.PageRouteInfo>? children}) +/// [_i18.SplashPage] +class SplashRoute extends _i19.PageRouteInfo { + const SplashRoute({List<_i19.PageRouteInfo>? children}) : super(SplashRoute.name, initialChildren: children); static const String name = 'SplashRoute'; - static _i18.PageInfo page = _i18.PageInfo( + static _i19.PageInfo page = _i19.PageInfo( name, builder: (data) { - return const _i17.SplashPage(); + return const _i18.SplashPage(); }, ); }