update order detail
This commit is contained in:
@@ -24,8 +24,8 @@ class OrderDetailModel {
|
||||
final List<OrderItemDetailModel> items;
|
||||
final PaymentTermsInfoModel paymentTerms;
|
||||
final List<TimelineItemModel> timeline;
|
||||
final List<dynamic> payments;
|
||||
final List<dynamic> invoices;
|
||||
final List<PaymentInfoModel> payments;
|
||||
final List<InvoiceInfoModel> invoices;
|
||||
|
||||
/// Create from JSON
|
||||
factory OrderDetailModel.fromJson(Map<String, dynamic> json) {
|
||||
@@ -50,8 +50,14 @@ class OrderDetailModel {
|
||||
.map((item) =>
|
||||
TimelineItemModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList(),
|
||||
payments: json['payments'] as List<dynamic>? ?? [],
|
||||
invoices: json['invoices'] as List<dynamic>? ?? [],
|
||||
payments: (json['payments'] as List<dynamic>? ?? [])
|
||||
.map((item) =>
|
||||
PaymentInfoModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList(),
|
||||
invoices: (json['invoices'] as List<dynamic>? ?? [])
|
||||
.map((item) =>
|
||||
InvoiceInfoModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,8 +70,8 @@ class OrderDetailModel {
|
||||
'items': items.map((item) => item.toJson()).toList(),
|
||||
'payment_terms': paymentTerms.toJson(),
|
||||
'timeline': timeline.map((item) => item.toJson()).toList(),
|
||||
'payments': payments,
|
||||
'invoices': invoices,
|
||||
'payments': payments.map((item) => item.toJson()).toList(),
|
||||
'invoices': invoices.map((item) => item.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,8 +84,8 @@ class OrderDetailModel {
|
||||
items: items.map((item) => item.toEntity()).toList(),
|
||||
paymentTerms: paymentTerms.toEntity(),
|
||||
timeline: timeline.map((item) => item.toEntity()).toList(),
|
||||
payments: payments,
|
||||
invoices: invoices,
|
||||
payments: payments.map((item) => item.toEntity()).toList(),
|
||||
invoices: invoices.map((item) => item.toEntity()).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,8 +102,12 @@ class OrderDetailModel {
|
||||
timeline: entity.timeline
|
||||
.map((item) => TimelineItemModel.fromEntity(item))
|
||||
.toList(),
|
||||
payments: entity.payments,
|
||||
invoices: entity.invoices,
|
||||
payments: entity.payments
|
||||
.map((item) => PaymentInfoModel.fromEntity(item))
|
||||
.toList(),
|
||||
invoices: entity.invoices
|
||||
.map((item) => InvoiceInfoModel.fromEntity(item))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -500,3 +510,93 @@ class TimelineItemModel {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Payment Info Model
|
||||
class PaymentInfoModel {
|
||||
const PaymentInfoModel({
|
||||
required this.name,
|
||||
required this.creationDate,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String creationDate;
|
||||
final double amount;
|
||||
|
||||
factory PaymentInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
return PaymentInfoModel(
|
||||
name: json['name'] as String,
|
||||
creationDate: json['creation_date'] as String,
|
||||
amount: (json['amount'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'creation_date': creationDate,
|
||||
'amount': amount,
|
||||
};
|
||||
}
|
||||
|
||||
PaymentInfo toEntity() {
|
||||
return PaymentInfo(
|
||||
name: name,
|
||||
creationDate: creationDate,
|
||||
amount: amount,
|
||||
);
|
||||
}
|
||||
|
||||
factory PaymentInfoModel.fromEntity(PaymentInfo entity) {
|
||||
return PaymentInfoModel(
|
||||
name: entity.name,
|
||||
creationDate: entity.creationDate,
|
||||
amount: entity.amount,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoice Info Model
|
||||
class InvoiceInfoModel {
|
||||
const InvoiceInfoModel({
|
||||
required this.name,
|
||||
required this.postingDate,
|
||||
required this.grandTotal,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String postingDate;
|
||||
final double grandTotal;
|
||||
|
||||
factory InvoiceInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
return InvoiceInfoModel(
|
||||
name: json['name'] as String,
|
||||
postingDate: json['posting_date'] as String,
|
||||
grandTotal: (json['grand_total'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'posting_date': postingDate,
|
||||
'grand_total': grandTotal,
|
||||
};
|
||||
}
|
||||
|
||||
InvoiceInfo toEntity() {
|
||||
return InvoiceInfo(
|
||||
name: name,
|
||||
postingDate: postingDate,
|
||||
grandTotal: grandTotal,
|
||||
);
|
||||
}
|
||||
|
||||
factory InvoiceInfoModel.fromEntity(InvoiceInfo entity) {
|
||||
return InvoiceInfoModel(
|
||||
name: entity.name,
|
||||
postingDate: entity.postingDate,
|
||||
grandTotal: entity.grandTotal,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ class OrderDetail extends Equatable {
|
||||
final List<OrderItemDetail> items;
|
||||
final PaymentTermsInfo paymentTerms;
|
||||
final List<TimelineItem> timeline;
|
||||
final List<dynamic> payments; // Payment entities can be added later
|
||||
final List<dynamic> invoices; // Invoice entities can be added later
|
||||
final List<PaymentInfo> payments;
|
||||
final List<InvoiceInfo> invoices;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
@@ -219,3 +219,35 @@ class TimelineItem extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [label, value, status];
|
||||
}
|
||||
|
||||
/// Payment Info
|
||||
class PaymentInfo extends Equatable {
|
||||
const PaymentInfo({
|
||||
required this.name,
|
||||
required this.creationDate,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String creationDate;
|
||||
final double amount;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, creationDate, amount];
|
||||
}
|
||||
|
||||
/// Invoice Info
|
||||
class InvoiceInfo extends Equatable {
|
||||
const InvoiceInfo({
|
||||
required this.name,
|
||||
required this.postingDate,
|
||||
required this.grandTotal,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String postingDate;
|
||||
final double grandTotal;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, postingDate, grandTotal];
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:intl/intl.dart';
|
||||
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/orders/domain/entities/order_detail.dart';
|
||||
import 'package:worker/features/orders/presentation/providers/orders_provider.dart';
|
||||
|
||||
@@ -82,16 +83,24 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
_buildStatusTimelineCard(orderDetail),
|
||||
|
||||
// Delivery/Address Information Card
|
||||
_buildAddressInfoCard(orderDetail),
|
||||
_buildAddressInfoCard(context, orderDetail),
|
||||
|
||||
// Customer Information Card
|
||||
_buildCustomerInfoCard(orderDetail),
|
||||
// Invoice Information Card
|
||||
_buildInvoiceInfoCard(context, orderDetail),
|
||||
|
||||
// Invoices List Card
|
||||
_buildInvoicesListCard(context, orderDetail),
|
||||
|
||||
// Products List Card
|
||||
_buildProductsListCard(orderDetail),
|
||||
|
||||
// Order Summary Card
|
||||
_buildOrderSummaryCard(orderDetail),
|
||||
|
||||
// Payment History Card
|
||||
_buildPaymentHistoryCard(context, orderDetail),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -327,7 +336,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
}
|
||||
|
||||
/// Build Address Info Card
|
||||
Widget _buildAddressInfoCard(OrderDetail orderDetail) {
|
||||
Widget _buildAddressInfoCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
final shippingAddress = orderDetail.shippingAddress;
|
||||
final dateFormatter = DateFormat('dd/MM/yyyy');
|
||||
@@ -341,15 +350,15 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
const Row(
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.truck,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Thông tin giao hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
@@ -362,86 +371,139 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Delivery Date
|
||||
_buildInfoRow(
|
||||
icon: FontAwesomeIcons.calendar,
|
||||
label: 'Ngày giao hàng',
|
||||
value: dateFormatter.format(DateTime.parse(order.deliveryDate)),
|
||||
// Address Section with Label + Button
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Địa chỉ nhận hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to address update
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: const BorderSide(color: AppColors.grey100),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Cập nhật',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_buildInfoRow(
|
||||
icon: FontAwesomeIcons.locationDot,
|
||||
label: 'Địa chỉ giao hàng',
|
||||
value:
|
||||
'${shippingAddress.addressLine1}\n${shippingAddress.wardName}, ${shippingAddress.cityName}',
|
||||
// Address Box
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
shippingAddress.addressTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
shippingAddress.phone,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${shippingAddress.addressLine1}\n${shippingAddress.wardName}, ${shippingAddress.cityName}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildInfoRow(
|
||||
icon: FontAwesomeIcons.user,
|
||||
label: 'Người nhận',
|
||||
value: '${shippingAddress.addressTitle} - ${shippingAddress.phone}',
|
||||
// Pickup Date
|
||||
const Text(
|
||||
'Ngày lấy hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
dateFormatter.format(DateTime.parse(order.deliveryDate)),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
|
||||
if (order.description.isNotEmpty) ...[ const SizedBox(height: 16),
|
||||
|
||||
// Notes
|
||||
const Text(
|
||||
'Ghi chú',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
order.description,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Info Row
|
||||
Widget _buildInfoRow({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required String value,
|
||||
Color? valueColor,
|
||||
}) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
children: [
|
||||
FaIcon(icon, size: 14, color: AppColors.grey500),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
value,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: valueColor != null
|
||||
? FontWeight.w600
|
||||
: FontWeight.w500,
|
||||
color: valueColor ?? AppColors.grey900,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Customer Info Card
|
||||
Widget _buildCustomerInfoCard(OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
/// Build Invoice Info Card
|
||||
Widget _buildInvoiceInfoCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final billingAddress = orderDetail.billingAddress;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
@@ -451,16 +513,146 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title + Update Button
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.user,
|
||||
const Row(
|
||||
children: [
|
||||
FaIcon(
|
||||
FontAwesomeIcons.fileInvoice,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Thông tin hóa đơn',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to invoice update
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: BorderSide(color: AppColors.grey100),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Cập nhật',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Invoice Address Box
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
billingAddress.addressTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
if (billingAddress.taxCode.isNotEmpty) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Mã số thuế: ${billingAddress.taxCode}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Số điện thoại: ${billingAddress.phone}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Email: ${billingAddress.email}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Địa chỉ: ${billingAddress.addressLine1}, ${billingAddress.wardName}, ${billingAddress.cityName}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Invoices List Card
|
||||
Widget _buildInvoicesListCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final invoices = orderDetail.invoices;
|
||||
|
||||
if (invoices.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
FaIcon(
|
||||
FontAwesomeIcons.fileInvoiceDollar,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'Thông tin khách hàng',
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Hóa đơn đã xuất',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -472,54 +664,109 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildCustomerRow('Tên khách hàng:', order.customer),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
_buildCustomerRow('Số điện thoại:', billingAddress.phone),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
_buildCustomerRow('Email:', billingAddress.email),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
if (billingAddress.taxCode.isNotEmpty) ...[
|
||||
_buildCustomerRow('Mã số thuế:', billingAddress.taxCode),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
// Invoice Items (Mock data for now)
|
||||
...invoices.map((e) => _buildInvoiceItem(
|
||||
invoiceId: e.name,
|
||||
date: e.postingDate,
|
||||
amount: e.grandTotal.toVNCurrency,
|
||||
onTap: () {
|
||||
// TODO: Navigate to invoice detail
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
);
|
||||
},
|
||||
),),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Customer Row
|
||||
Widget _buildCustomerRow(String label, String value) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
/// Build Invoice Item
|
||||
Widget _buildInvoiceItem({
|
||||
required String invoiceId,
|
||||
required String date,
|
||||
required String amount,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [AppColors.primaryBlue, Color(0xFF1d4ed8)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.fileInvoice,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
invoiceId,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Ngày xuất: $date',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4,),
|
||||
Text(
|
||||
amount,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.danger,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.chevronRight,
|
||||
size: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Products List Card
|
||||
Widget _buildProductsListCard(OrderDetail orderDetail) {
|
||||
final items = orderDetail.items;
|
||||
final currencyFormatter = NumberFormat.currency(
|
||||
locale: 'vi_VN',
|
||||
symbol: 'đ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
@@ -530,15 +777,15 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
const Row(
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.box,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Sản phẩm đặt hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
@@ -657,14 +904,14 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${currencyFormatter.format(item.price)}/m²',
|
||||
'${item.price.toVNCurrency}/m²',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
currencyFormatter.format(item.totalAmount),
|
||||
item.totalAmount.toVNCurrency,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -692,11 +939,6 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
Widget _buildOrderSummaryCard(OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
final paymentTerms = orderDetail.paymentTerms;
|
||||
final currencyFormatter = NumberFormat.currency(
|
||||
locale: 'vi_VN',
|
||||
symbol: 'đ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
@@ -728,13 +970,13 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildSummaryRow('Tổng tiền hàng:', currencyFormatter.format(order.total)),
|
||||
_buildSummaryRow('Tổng tiền hàng:', order.total.toVNCurrency),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
if (order.totalRemaining > 0) ...[
|
||||
_buildSummaryRow(
|
||||
'Còn lại:',
|
||||
currencyFormatter.format(order.totalRemaining),
|
||||
order.totalRemaining.toVNCurrency,
|
||||
valueColor: AppColors.warning,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -744,7 +986,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
_buildSummaryRow(
|
||||
'Tổng cộng:',
|
||||
currencyFormatter.format(order.grandTotal),
|
||||
order.grandTotal.toVNCurrency,
|
||||
isTotal: true,
|
||||
),
|
||||
|
||||
@@ -784,31 +1026,169 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
|
||||
if (order.description.isNotEmpty) ...[
|
||||
const Divider(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Order Notes
|
||||
Row(
|
||||
/// Build Payment History Card
|
||||
Widget _buildPaymentHistoryCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
final payments = orderDetail.payments;
|
||||
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 16,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
FaIcon(
|
||||
FontAwesomeIcons.clockRotateLeft,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Lịch sử thanh toán',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
...payments.map((e) => _buildPaymentItem(
|
||||
paymentId: e.name,
|
||||
date: e.creationDate,
|
||||
amount: e.amount.toVNCurrency,
|
||||
onTap: () {
|
||||
// TODO: Show payment detail modal
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chi tiết thanh toán')),
|
||||
);
|
||||
},
|
||||
)),
|
||||
|
||||
|
||||
// Payment Summary
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(color: AppColors.grey100, width: 1),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const FaIcon(FontAwesomeIcons.noteSticky, size: 14, color: AppColors.grey500),
|
||||
const SizedBox(width: 6),
|
||||
const Text(
|
||||
'Ghi chú đơn hàng:',
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
'Còn lại:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
order.totalRemaining.toVNCurrency,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.danger,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
order.description,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
height: 1.4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Payment Item
|
||||
Widget _buildPaymentItem({
|
||||
required String paymentId,
|
||||
required String date,
|
||||
required String amount,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFD1FAE5),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.check,
|
||||
color: Color(0xFF065F46),
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
paymentId,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
date,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
amount,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF065F46),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.chevronRight,
|
||||
size: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -848,8 +1228,6 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
/// Build Action Buttons
|
||||
Widget _buildActionButtons(BuildContext context, OrderDetail orderDetail) {
|
||||
final shippingAddress = orderDetail.shippingAddress;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
@@ -866,23 +1244,22 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
spacing: 12,
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to payment page
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Gọi ${shippingAddress.phone}...'),
|
||||
const SnackBar(
|
||||
content: Text('Chức năng thanh toán đang phát triển'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.phone, size: 18),
|
||||
label: const Text('Liên hệ'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
side: const BorderSide(
|
||||
color: AppColors.grey100,
|
||||
width: 2,
|
||||
),
|
||||
foregroundColor: AppColors.grey900,
|
||||
icon: const FaIcon(FontAwesomeIcons.creditCard, size: 18),
|
||||
label: const Text('Thanh toán'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
@@ -890,21 +1267,24 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to chat/support
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Chức năng đang phát triển...'),
|
||||
content: Text('Liên hệ hỗ trợ...'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.penToSquare, size: 18),
|
||||
label: const Text('Cập nhật'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
icon: const FaIcon(FontAwesomeIcons.comments, size: 18),
|
||||
label: const Text('Liên hệ hỗ trợ'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
side: BorderSide(
|
||||
color: AppColors.grey100,
|
||||
width: 2,
|
||||
),
|
||||
foregroundColor: AppColors.grey900,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user