update address, cancel order
This commit is contained in:
@@ -13,6 +13,7 @@ import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/enums/status_color.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/core/utils/extensions.dart';
|
||||
import 'package:worker/features/account/domain/entities/address.dart';
|
||||
import 'package:worker/features/orders/domain/entities/order_detail.dart';
|
||||
import 'package:worker/features/orders/presentation/providers/orders_provider.dart';
|
||||
|
||||
@@ -33,13 +34,21 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final orderDetailAsync = ref.watch(orderDetailProvider(orderId));
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF4F6F8),
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
return PopScope(
|
||||
onPopInvoked: (didPop) {
|
||||
if (didPop) {
|
||||
// Dispose providers when leaving the page
|
||||
ref.invalidate(updateOrderAddressProvider);
|
||||
ref.invalidate(cancelOrderProvider);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: const Color(0xFFF4F6F8),
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
title: const Text(
|
||||
'Chi tiết đơn hàng',
|
||||
style: TextStyle(color: Colors.black),
|
||||
@@ -83,10 +92,10 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
_buildStatusTimelineCard(orderDetail),
|
||||
|
||||
// Delivery/Address Information Card
|
||||
_buildAddressInfoCard(context, orderDetail),
|
||||
_buildAddressInfoCard(context, ref, orderDetail),
|
||||
|
||||
// Invoice Information Card
|
||||
_buildInvoiceInfoCard(context, orderDetail),
|
||||
_buildInvoiceInfoCard(context, ref, orderDetail),
|
||||
|
||||
// Invoices List Card
|
||||
_buildInvoicesListCard(context, orderDetail),
|
||||
@@ -98,7 +107,15 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
_buildOrderSummaryCard(orderDetail),
|
||||
|
||||
// Payment History Card
|
||||
_buildPaymentHistoryCard(context, orderDetail),
|
||||
if (orderDetail.order.totalRemaining > 0) ...[
|
||||
_buildPaymentHistoryCard(context, orderDetail),
|
||||
],
|
||||
|
||||
// Cancel Order Button (only show for "Chờ phê duyệt" status)
|
||||
if (orderDetail.order.status == 'Chờ phê duyệt') ...[
|
||||
const SizedBox(height: 16),
|
||||
_buildCancelOrderButton(context, ref, orderDetail),
|
||||
],
|
||||
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
@@ -163,6 +180,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -336,7 +354,11 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
}
|
||||
|
||||
/// Build Address Info Card
|
||||
Widget _buildAddressInfoCard(BuildContext context, OrderDetail orderDetail) {
|
||||
Widget _buildAddressInfoCard(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
OrderDetail orderDetail,
|
||||
) {
|
||||
final order = orderDetail.order;
|
||||
final shippingAddress = orderDetail.shippingAddress;
|
||||
final dateFormatter = DateFormat('dd/MM/yyyy');
|
||||
@@ -384,11 +406,71 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to address update
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
onPressed: () async {
|
||||
// Navigate to address selection and wait for result
|
||||
final result = await context.push<Address>(
|
||||
'/account/addresses',
|
||||
extra: {
|
||||
'selectMode': true,
|
||||
'currentAddress': shippingAddress,
|
||||
},
|
||||
);
|
||||
|
||||
// If user selected an address, update the order
|
||||
if (result != null && context.mounted) {
|
||||
// Show loading indicator
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Đang cập nhật địa chỉ...'),
|
||||
duration: Duration(seconds: 1),
|
||||
),
|
||||
);
|
||||
|
||||
// Update shipping address (keep billing address the same)
|
||||
await ref
|
||||
.read(updateOrderAddressProvider.notifier)
|
||||
.updateAddress(
|
||||
orderId: orderId,
|
||||
shippingAddressName: result.name,
|
||||
customerAddress: orderDetail.billingAddress.name,
|
||||
);
|
||||
|
||||
// Check if update was successful
|
||||
final updateState =
|
||||
ref.read(updateOrderAddressProvider)
|
||||
..when(
|
||||
data: (_) {
|
||||
// Refresh order detail to show updated address
|
||||
ref.invalidate(orderDetailProvider(orderId));
|
||||
|
||||
// Show success message
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Cập nhật địa chỉ giao hàng thành công',
|
||||
),
|
||||
backgroundColor: AppColors.success,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
error: (error, _) {
|
||||
// Show error message
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Lỗi: ${error.toString()}',
|
||||
),
|
||||
backgroundColor: AppColors.danger,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
loading: () {},
|
||||
);
|
||||
}
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
@@ -501,7 +583,11 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
}
|
||||
|
||||
/// Build Invoice Info Card
|
||||
Widget _buildInvoiceInfoCard(BuildContext context, OrderDetail orderDetail) {
|
||||
Widget _buildInvoiceInfoCard(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
OrderDetail orderDetail,
|
||||
) {
|
||||
final billingAddress = orderDetail.billingAddress;
|
||||
|
||||
return Card(
|
||||
@@ -536,17 +622,77 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to invoice update
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
onPressed: () async {
|
||||
// Navigate to address selection and wait for result
|
||||
final result = await context.push<Address>(
|
||||
'/account/addresses',
|
||||
extra: {
|
||||
'selectMode': true,
|
||||
'currentAddress': billingAddress,
|
||||
},
|
||||
);
|
||||
|
||||
// If user selected an address, update the order
|
||||
if (result != null && context.mounted) {
|
||||
// Show loading indicator
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Đang cập nhật địa chỉ...'),
|
||||
duration: Duration(seconds: 1),
|
||||
),
|
||||
);
|
||||
|
||||
// Update billing address (keep shipping address the same)
|
||||
await ref
|
||||
.read(updateOrderAddressProvider.notifier)
|
||||
.updateAddress(
|
||||
orderId: orderId,
|
||||
shippingAddressName: orderDetail.shippingAddress.name,
|
||||
customerAddress: result.name,
|
||||
);
|
||||
|
||||
// Check if update was successful
|
||||
final updateState =
|
||||
ref.read(updateOrderAddressProvider)
|
||||
..when(
|
||||
data: (_) {
|
||||
// Refresh order detail to show updated address
|
||||
ref.invalidate(orderDetailProvider(orderId));
|
||||
|
||||
// Show success message
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Cập nhật địa chỉ hóa đơn thành công',
|
||||
),
|
||||
backgroundColor: AppColors.success,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
error: (error, _) {
|
||||
// Show error message
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Lỗi: ${error.toString()}',
|
||||
),
|
||||
backgroundColor: AppColors.danger,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
loading: () {},
|
||||
);
|
||||
}
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: BorderSide(color: AppColors.grey100),
|
||||
side: const BorderSide(color: AppColors.grey100),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
@@ -583,7 +729,8 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
if (billingAddress.taxCode.isNotEmpty) ...[
|
||||
if (billingAddress.taxCode != null &&
|
||||
billingAddress.taxCode!.isNotEmpty) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Mã số thuế: ${billingAddress.taxCode}',
|
||||
@@ -949,15 +1096,15 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
const Row(
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.receipt,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Tổng kết đơn hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
@@ -993,15 +1140,15 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
const Divider(height: 24),
|
||||
|
||||
// Payment Terms
|
||||
Row(
|
||||
const Row(
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.creditCard,
|
||||
size: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Text(
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'Điều khoản thanh toán:',
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
@@ -1295,4 +1442,116 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Cancel Order Button
|
||||
Widget _buildCancelOrderButton(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
OrderDetail orderDetail,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () async {
|
||||
// Show confirmation dialog
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Xác nhận hủy đơn'),
|
||||
content: Text(
|
||||
'Bạn có chắc chắn muốn hủy đơn hàng ${orderDetail.order.name}?\n\nHành động này không thể hoàn tác.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Không'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.danger,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Hủy đơn'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// If user confirmed, proceed with cancellation
|
||||
if (confirmed == true && context.mounted) {
|
||||
// Show loading indicator
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Text('Đang hủy đơn hàng...'),
|
||||
],
|
||||
),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
|
||||
// Call cancel order API
|
||||
await ref.read(cancelOrderProvider.notifier).cancel(orderId);
|
||||
|
||||
// Check result
|
||||
final cancelState = ref.read(cancelOrderProvider);
|
||||
if (context.mounted) {
|
||||
cancelState.when(
|
||||
data: (_) {
|
||||
// Success: Invalidate order providers and show success message
|
||||
ref.invalidate(ordersProvider);
|
||||
ref.invalidate(orderDetailProvider(orderId));
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Đã hủy đơn hàng thành công'),
|
||||
backgroundColor: AppColors.success,
|
||||
),
|
||||
);
|
||||
|
||||
// Pop back to orders list
|
||||
context.pop();
|
||||
},
|
||||
error: (error, _) {
|
||||
// Error: Show error message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Lỗi: ${error.toString()}'),
|
||||
backgroundColor: AppColors.danger,
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () {},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.xmark, size: 18),
|
||||
label: const Text('Hủy đơn hàng'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
side: const BorderSide(
|
||||
color: AppColors.danger,
|
||||
width: 2,
|
||||
),
|
||||
foregroundColor: AppColors.danger,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user