add auth, format
This commit is contained in:
@@ -27,7 +27,9 @@ class InvoicesLocalDataSource {
|
||||
.map((json) => InvoiceModel.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
debugPrint('[InvoicesLocalDataSource] Loaded ${invoices.length} invoices');
|
||||
debugPrint(
|
||||
'[InvoicesLocalDataSource] Loaded ${invoices.length} invoices',
|
||||
);
|
||||
return invoices;
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('[InvoicesLocalDataSource] Error loading invoices: $e');
|
||||
@@ -65,8 +67,11 @@ class InvoicesLocalDataSource {
|
||||
final filtered = allInvoices
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.invoiceNumber.toLowerCase().contains(query.toLowerCase()) ||
|
||||
(invoice.orderId?.toLowerCase().contains(query.toLowerCase()) ?? false),
|
||||
invoice.invoiceNumber.toLowerCase().contains(
|
||||
query.toLowerCase(),
|
||||
) ||
|
||||
(invoice.orderId?.toLowerCase().contains(query.toLowerCase()) ??
|
||||
false),
|
||||
)
|
||||
.toList();
|
||||
|
||||
@@ -89,7 +94,9 @@ class InvoicesLocalDataSource {
|
||||
orElse: () => throw Exception('Invoice not found: $invoiceId'),
|
||||
);
|
||||
|
||||
debugPrint('[InvoicesLocalDataSource] Found invoice: ${invoice.invoiceNumber}');
|
||||
debugPrint(
|
||||
'[InvoicesLocalDataSource] Found invoice: ${invoice.invoiceNumber}',
|
||||
);
|
||||
return invoice;
|
||||
} catch (e) {
|
||||
debugPrint('[InvoicesLocalDataSource] Error getting invoice: $e');
|
||||
@@ -105,10 +112,14 @@ class InvoicesLocalDataSource {
|
||||
.where((invoice) => invoice.isOverdue)
|
||||
.toList();
|
||||
|
||||
debugPrint('[InvoicesLocalDataSource] Found ${overdue.length} overdue invoices');
|
||||
debugPrint(
|
||||
'[InvoicesLocalDataSource] Found ${overdue.length} overdue invoices',
|
||||
);
|
||||
return overdue;
|
||||
} catch (e) {
|
||||
debugPrint('[InvoicesLocalDataSource] Error getting overdue invoices: $e');
|
||||
debugPrint(
|
||||
'[InvoicesLocalDataSource] Error getting overdue invoices: $e',
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -118,10 +129,14 @@ class InvoicesLocalDataSource {
|
||||
try {
|
||||
final allInvoices = await getAllInvoices();
|
||||
final unpaid = allInvoices
|
||||
.where((invoice) => invoice.status.name == 'issued' && !invoice.isPaid)
|
||||
.where(
|
||||
(invoice) => invoice.status.name == 'issued' && !invoice.isPaid,
|
||||
)
|
||||
.toList();
|
||||
|
||||
debugPrint('[InvoicesLocalDataSource] Found ${unpaid.length} unpaid invoices');
|
||||
debugPrint(
|
||||
'[InvoicesLocalDataSource] Found ${unpaid.length} unpaid invoices',
|
||||
);
|
||||
return unpaid;
|
||||
} catch (e) {
|
||||
debugPrint('[InvoicesLocalDataSource] Error getting unpaid invoices: $e');
|
||||
|
||||
@@ -6,37 +6,84 @@ part 'invoice_model.g.dart';
|
||||
|
||||
@HiveType(typeId: HiveTypeIds.invoiceModel)
|
||||
class InvoiceModel extends HiveObject {
|
||||
InvoiceModel({required this.invoiceId, required this.invoiceNumber, required this.userId, this.orderId, required this.invoiceType, required this.issueDate, required this.dueDate, required this.currency, required this.subtotalAmount, required this.taxAmount, required this.discountAmount, required this.shippingAmount, required this.totalAmount, required this.amountPaid, required this.amountRemaining, required this.status, this.paymentTerms, this.notes, this.erpnextInvoice, required this.createdAt, this.updatedAt, this.lastReminderSent});
|
||||
|
||||
@HiveField(0) final String invoiceId;
|
||||
@HiveField(1) final String invoiceNumber;
|
||||
@HiveField(2) final String userId;
|
||||
@HiveField(3) final String? orderId;
|
||||
@HiveField(4) final InvoiceType invoiceType;
|
||||
@HiveField(5) final DateTime issueDate;
|
||||
@HiveField(6) final DateTime dueDate;
|
||||
@HiveField(7) final String currency;
|
||||
@HiveField(8) final double subtotalAmount;
|
||||
@HiveField(9) final double taxAmount;
|
||||
@HiveField(10) final double discountAmount;
|
||||
@HiveField(11) final double shippingAmount;
|
||||
@HiveField(12) final double totalAmount;
|
||||
@HiveField(13) final double amountPaid;
|
||||
@HiveField(14) final double amountRemaining;
|
||||
@HiveField(15) final InvoiceStatus status;
|
||||
@HiveField(16) final String? paymentTerms;
|
||||
@HiveField(17) final String? notes;
|
||||
@HiveField(18) final String? erpnextInvoice;
|
||||
@HiveField(19) final DateTime createdAt;
|
||||
@HiveField(20) final DateTime? updatedAt;
|
||||
@HiveField(21) final DateTime? lastReminderSent;
|
||||
InvoiceModel({
|
||||
required this.invoiceId,
|
||||
required this.invoiceNumber,
|
||||
required this.userId,
|
||||
this.orderId,
|
||||
required this.invoiceType,
|
||||
required this.issueDate,
|
||||
required this.dueDate,
|
||||
required this.currency,
|
||||
required this.subtotalAmount,
|
||||
required this.taxAmount,
|
||||
required this.discountAmount,
|
||||
required this.shippingAmount,
|
||||
required this.totalAmount,
|
||||
required this.amountPaid,
|
||||
required this.amountRemaining,
|
||||
required this.status,
|
||||
this.paymentTerms,
|
||||
this.notes,
|
||||
this.erpnextInvoice,
|
||||
required this.createdAt,
|
||||
this.updatedAt,
|
||||
this.lastReminderSent,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
final String invoiceId;
|
||||
@HiveField(1)
|
||||
final String invoiceNumber;
|
||||
@HiveField(2)
|
||||
final String userId;
|
||||
@HiveField(3)
|
||||
final String? orderId;
|
||||
@HiveField(4)
|
||||
final InvoiceType invoiceType;
|
||||
@HiveField(5)
|
||||
final DateTime issueDate;
|
||||
@HiveField(6)
|
||||
final DateTime dueDate;
|
||||
@HiveField(7)
|
||||
final String currency;
|
||||
@HiveField(8)
|
||||
final double subtotalAmount;
|
||||
@HiveField(9)
|
||||
final double taxAmount;
|
||||
@HiveField(10)
|
||||
final double discountAmount;
|
||||
@HiveField(11)
|
||||
final double shippingAmount;
|
||||
@HiveField(12)
|
||||
final double totalAmount;
|
||||
@HiveField(13)
|
||||
final double amountPaid;
|
||||
@HiveField(14)
|
||||
final double amountRemaining;
|
||||
@HiveField(15)
|
||||
final InvoiceStatus status;
|
||||
@HiveField(16)
|
||||
final String? paymentTerms;
|
||||
@HiveField(17)
|
||||
final String? notes;
|
||||
@HiveField(18)
|
||||
final String? erpnextInvoice;
|
||||
@HiveField(19)
|
||||
final DateTime createdAt;
|
||||
@HiveField(20)
|
||||
final DateTime? updatedAt;
|
||||
@HiveField(21)
|
||||
final DateTime? lastReminderSent;
|
||||
|
||||
factory InvoiceModel.fromJson(Map<String, dynamic> json) => InvoiceModel(
|
||||
invoiceId: json['invoice_id'] as String,
|
||||
invoiceNumber: json['invoice_number'] as String,
|
||||
userId: json['user_id'] as String,
|
||||
orderId: json['order_id'] as String?,
|
||||
invoiceType: InvoiceType.values.firstWhere((e) => e.name == json['invoice_type']),
|
||||
invoiceType: InvoiceType.values.firstWhere(
|
||||
(e) => e.name == json['invoice_type'],
|
||||
),
|
||||
issueDate: DateTime.parse(json['issue_date']?.toString() ?? ''),
|
||||
dueDate: DateTime.parse(json['due_date']?.toString() ?? ''),
|
||||
currency: json['currency'] as String? ?? 'VND',
|
||||
@@ -52,8 +99,12 @@ class InvoiceModel extends HiveObject {
|
||||
notes: json['notes'] as String?,
|
||||
erpnextInvoice: json['erpnext_invoice'] as String?,
|
||||
createdAt: DateTime.parse(json['created_at']?.toString() ?? ''),
|
||||
updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']?.toString() ?? '') : null,
|
||||
lastReminderSent: json['last_reminder_sent'] != null ? DateTime.parse(json['last_reminder_sent']?.toString() ?? '') : null,
|
||||
updatedAt: json['updated_at'] != null
|
||||
? DateTime.parse(json['updated_at']?.toString() ?? '')
|
||||
: null,
|
||||
lastReminderSent: json['last_reminder_sent'] != null
|
||||
? DateTime.parse(json['last_reminder_sent']?.toString() ?? '')
|
||||
: null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@@ -81,6 +132,7 @@ class InvoiceModel extends HiveObject {
|
||||
'last_reminder_sent': lastReminderSent?.toIso8601String(),
|
||||
};
|
||||
|
||||
bool get isOverdue => DateTime.now().isAfter(dueDate) && status != InvoiceStatus.paid;
|
||||
bool get isOverdue =>
|
||||
DateTime.now().isAfter(dueDate) && status != InvoiceStatus.paid;
|
||||
bool get isPaid => status == InvoiceStatus.paid;
|
||||
}
|
||||
|
||||
@@ -5,16 +5,33 @@ part 'order_item_model.g.dart';
|
||||
|
||||
@HiveType(typeId: HiveTypeIds.orderItemModel)
|
||||
class OrderItemModel extends HiveObject {
|
||||
OrderItemModel({required this.orderItemId, required this.orderId, required this.productId, required this.quantity, required this.unitPrice, required this.discountPercent, required this.subtotal, this.notes});
|
||||
|
||||
@HiveField(0) final String orderItemId;
|
||||
@HiveField(1) final String orderId;
|
||||
@HiveField(2) final String productId;
|
||||
@HiveField(3) final double quantity;
|
||||
@HiveField(4) final double unitPrice;
|
||||
@HiveField(5) final double discountPercent;
|
||||
@HiveField(6) final double subtotal;
|
||||
@HiveField(7) final String? notes;
|
||||
OrderItemModel({
|
||||
required this.orderItemId,
|
||||
required this.orderId,
|
||||
required this.productId,
|
||||
required this.quantity,
|
||||
required this.unitPrice,
|
||||
required this.discountPercent,
|
||||
required this.subtotal,
|
||||
this.notes,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
final String orderItemId;
|
||||
@HiveField(1)
|
||||
final String orderId;
|
||||
@HiveField(2)
|
||||
final String productId;
|
||||
@HiveField(3)
|
||||
final double quantity;
|
||||
@HiveField(4)
|
||||
final double unitPrice;
|
||||
@HiveField(5)
|
||||
final double discountPercent;
|
||||
@HiveField(6)
|
||||
final double subtotal;
|
||||
@HiveField(7)
|
||||
final String? notes;
|
||||
|
||||
factory OrderItemModel.fromJson(Map<String, dynamic> json) => OrderItemModel(
|
||||
orderItemId: json['order_item_id'] as String,
|
||||
|
||||
@@ -94,15 +94,25 @@ class OrderModel extends HiveObject {
|
||||
taxAmount: (json['tax_amount'] as num).toDouble(),
|
||||
shippingFee: (json['shipping_fee'] as num).toDouble(),
|
||||
finalAmount: (json['final_amount'] as num).toDouble(),
|
||||
shippingAddress: json['shipping_address'] != null ? jsonEncode(json['shipping_address']) : null,
|
||||
billingAddress: json['billing_address'] != null ? jsonEncode(json['billing_address']) : null,
|
||||
expectedDeliveryDate: json['expected_delivery_date'] != null ? DateTime.parse(json['expected_delivery_date']?.toString() ?? '') : null,
|
||||
actualDeliveryDate: json['actual_delivery_date'] != null ? DateTime.parse(json['actual_delivery_date']?.toString() ?? '') : null,
|
||||
shippingAddress: json['shipping_address'] != null
|
||||
? jsonEncode(json['shipping_address'])
|
||||
: null,
|
||||
billingAddress: json['billing_address'] != null
|
||||
? jsonEncode(json['billing_address'])
|
||||
: null,
|
||||
expectedDeliveryDate: json['expected_delivery_date'] != null
|
||||
? DateTime.parse(json['expected_delivery_date']?.toString() ?? '')
|
||||
: null,
|
||||
actualDeliveryDate: json['actual_delivery_date'] != null
|
||||
? DateTime.parse(json['actual_delivery_date']?.toString() ?? '')
|
||||
: null,
|
||||
notes: json['notes'] as String?,
|
||||
cancellationReason: json['cancellation_reason'] as String?,
|
||||
erpnextSalesOrder: json['erpnext_sales_order'] as String?,
|
||||
createdAt: DateTime.parse(json['created_at']?.toString() ?? ''),
|
||||
updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']?.toString() ?? '') : null,
|
||||
updatedAt: json['updated_at'] != null
|
||||
? DateTime.parse(json['updated_at']?.toString() ?? '')
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,8 +126,12 @@ class OrderModel extends HiveObject {
|
||||
'tax_amount': taxAmount,
|
||||
'shipping_fee': shippingFee,
|
||||
'final_amount': finalAmount,
|
||||
'shipping_address': shippingAddress != null ? jsonDecode(shippingAddress!) : null,
|
||||
'billing_address': billingAddress != null ? jsonDecode(billingAddress!) : null,
|
||||
'shipping_address': shippingAddress != null
|
||||
? jsonDecode(shippingAddress!)
|
||||
: null,
|
||||
'billing_address': billingAddress != null
|
||||
? jsonDecode(billingAddress!)
|
||||
: null,
|
||||
'expected_delivery_date': expectedDeliveryDate?.toIso8601String(),
|
||||
'actual_delivery_date': actualDeliveryDate?.toIso8601String(),
|
||||
'notes': notes,
|
||||
|
||||
@@ -6,41 +6,79 @@ part 'payment_line_model.g.dart';
|
||||
|
||||
@HiveType(typeId: HiveTypeIds.paymentLineModel)
|
||||
class PaymentLineModel extends HiveObject {
|
||||
PaymentLineModel({required this.paymentLineId, required this.invoiceId, required this.paymentNumber, required this.paymentDate, required this.amount, required this.paymentMethod, this.bankName, this.bankAccount, this.referenceNumber, this.notes, required this.status, this.receiptUrl, this.erpnextPaymentEntry, required this.createdAt, this.processedAt});
|
||||
|
||||
@HiveField(0) final String paymentLineId;
|
||||
@HiveField(1) final String invoiceId;
|
||||
@HiveField(2) final String paymentNumber;
|
||||
@HiveField(3) final DateTime paymentDate;
|
||||
@HiveField(4) final double amount;
|
||||
@HiveField(5) final PaymentMethod paymentMethod;
|
||||
@HiveField(6) final String? bankName;
|
||||
@HiveField(7) final String? bankAccount;
|
||||
@HiveField(8) final String? referenceNumber;
|
||||
@HiveField(9) final String? notes;
|
||||
@HiveField(10) final PaymentStatus status;
|
||||
@HiveField(11) final String? receiptUrl;
|
||||
@HiveField(12) final String? erpnextPaymentEntry;
|
||||
@HiveField(13) final DateTime createdAt;
|
||||
@HiveField(14) final DateTime? processedAt;
|
||||
PaymentLineModel({
|
||||
required this.paymentLineId,
|
||||
required this.invoiceId,
|
||||
required this.paymentNumber,
|
||||
required this.paymentDate,
|
||||
required this.amount,
|
||||
required this.paymentMethod,
|
||||
this.bankName,
|
||||
this.bankAccount,
|
||||
this.referenceNumber,
|
||||
this.notes,
|
||||
required this.status,
|
||||
this.receiptUrl,
|
||||
this.erpnextPaymentEntry,
|
||||
required this.createdAt,
|
||||
this.processedAt,
|
||||
});
|
||||
|
||||
factory PaymentLineModel.fromJson(Map<String, dynamic> json) => PaymentLineModel(
|
||||
paymentLineId: json['payment_line_id'] as String,
|
||||
invoiceId: json['invoice_id'] as String,
|
||||
paymentNumber: json['payment_number'] as String,
|
||||
paymentDate: DateTime.parse(json['payment_date']?.toString() ?? ''),
|
||||
amount: (json['amount'] as num).toDouble(),
|
||||
paymentMethod: PaymentMethod.values.firstWhere((e) => e.name == json['payment_method']),
|
||||
bankName: json['bank_name'] as String?,
|
||||
bankAccount: json['bank_account'] as String?,
|
||||
referenceNumber: json['reference_number'] as String?,
|
||||
notes: json['notes'] as String?,
|
||||
status: PaymentStatus.values.firstWhere((e) => e.name == json['status']),
|
||||
receiptUrl: json['receipt_url'] as String?,
|
||||
erpnextPaymentEntry: json['erpnext_payment_entry'] as String?,
|
||||
createdAt: DateTime.parse(json['created_at']?.toString() ?? ''),
|
||||
processedAt: json['processed_at'] != null ? DateTime.parse(json['processed_at']?.toString() ?? '') : null,
|
||||
);
|
||||
@HiveField(0)
|
||||
final String paymentLineId;
|
||||
@HiveField(1)
|
||||
final String invoiceId;
|
||||
@HiveField(2)
|
||||
final String paymentNumber;
|
||||
@HiveField(3)
|
||||
final DateTime paymentDate;
|
||||
@HiveField(4)
|
||||
final double amount;
|
||||
@HiveField(5)
|
||||
final PaymentMethod paymentMethod;
|
||||
@HiveField(6)
|
||||
final String? bankName;
|
||||
@HiveField(7)
|
||||
final String? bankAccount;
|
||||
@HiveField(8)
|
||||
final String? referenceNumber;
|
||||
@HiveField(9)
|
||||
final String? notes;
|
||||
@HiveField(10)
|
||||
final PaymentStatus status;
|
||||
@HiveField(11)
|
||||
final String? receiptUrl;
|
||||
@HiveField(12)
|
||||
final String? erpnextPaymentEntry;
|
||||
@HiveField(13)
|
||||
final DateTime createdAt;
|
||||
@HiveField(14)
|
||||
final DateTime? processedAt;
|
||||
|
||||
factory PaymentLineModel.fromJson(Map<String, dynamic> json) =>
|
||||
PaymentLineModel(
|
||||
paymentLineId: json['payment_line_id'] as String,
|
||||
invoiceId: json['invoice_id'] as String,
|
||||
paymentNumber: json['payment_number'] as String,
|
||||
paymentDate: DateTime.parse(json['payment_date']?.toString() ?? ''),
|
||||
amount: (json['amount'] as num).toDouble(),
|
||||
paymentMethod: PaymentMethod.values.firstWhere(
|
||||
(e) => e.name == json['payment_method'],
|
||||
),
|
||||
bankName: json['bank_name'] as String?,
|
||||
bankAccount: json['bank_account'] as String?,
|
||||
referenceNumber: json['reference_number'] as String?,
|
||||
notes: json['notes'] as String?,
|
||||
status: PaymentStatus.values.firstWhere(
|
||||
(e) => e.name == json['status'],
|
||||
),
|
||||
receiptUrl: json['receipt_url'] as String?,
|
||||
erpnextPaymentEntry: json['erpnext_payment_entry'] as String?,
|
||||
createdAt: DateTime.parse(json['created_at']?.toString() ?? ''),
|
||||
processedAt: json['processed_at'] != null
|
||||
? DateTime.parse(json['processed_at']?.toString() ?? '')
|
||||
: null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'payment_line_id': paymentLineId,
|
||||
|
||||
@@ -15,7 +15,7 @@ enum InvoiceType {
|
||||
creditNote,
|
||||
|
||||
/// Debit note
|
||||
debitNote;
|
||||
debitNote,
|
||||
}
|
||||
|
||||
/// Invoice status enum
|
||||
@@ -166,8 +166,7 @@ class Invoice {
|
||||
(!isPaid && DateTime.now().isAfter(dueDate));
|
||||
|
||||
/// Check if invoice is partially paid
|
||||
bool get isPartiallyPaid =>
|
||||
amountPaid > 0 && amountPaid < totalAmount;
|
||||
bool get isPartiallyPaid => amountPaid > 0 && amountPaid < totalAmount;
|
||||
|
||||
/// Get payment percentage
|
||||
double get paymentPercentage {
|
||||
|
||||
@@ -50,8 +50,7 @@ class OrderItem {
|
||||
double get subtotalBeforeDiscount => quantity * unitPrice;
|
||||
|
||||
/// Calculate discount amount
|
||||
double get discountAmount =>
|
||||
subtotalBeforeDiscount * (discountPercent / 100);
|
||||
double get discountAmount => subtotalBeforeDiscount * (discountPercent / 100);
|
||||
|
||||
/// Calculate subtotal after discount (for verification)
|
||||
double get calculatedSubtotal => subtotalBeforeDiscount - discountAmount;
|
||||
|
||||
@@ -21,11 +21,7 @@ import 'package:worker/core/theme/colors.dart';
|
||||
/// - Order summary
|
||||
/// - Action buttons (Contact customer, Update status)
|
||||
class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const OrderDetailPage({
|
||||
required this.orderId,
|
||||
super.key,
|
||||
});
|
||||
const OrderDetailPage({required this.orderId, super.key});
|
||||
final String orderId;
|
||||
|
||||
@override
|
||||
@@ -51,7 +47,9 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
onPressed: () {
|
||||
// TODO: Implement share functionality
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng chia sẻ đang phát triển')),
|
||||
const SnackBar(
|
||||
content: Text('Chức năng chia sẻ đang phát triển'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -142,14 +140,19 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
onPressed: () {
|
||||
// TODO: Implement contact customer
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Gọi điện cho khách hàng...')),
|
||||
const SnackBar(
|
||||
content: Text('Gọi điện cho khách hàng...'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.phone),
|
||||
label: const Text('Liên hệ khách hàng'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
side: const BorderSide(color: AppColors.grey100, width: 2),
|
||||
side: const BorderSide(
|
||||
color: AppColors.grey100,
|
||||
width: 2,
|
||||
),
|
||||
foregroundColor: AppColors.grey900,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
@@ -162,7 +165,9 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
onPressed: () {
|
||||
// TODO: Implement update status
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Cập nhật trạng thái...')),
|
||||
const SnackBar(
|
||||
content: Text('Cập nhật trạng thái...'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.edit),
|
||||
@@ -196,9 +201,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(16),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
@@ -282,11 +285,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
color: iconBgColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
iconData,
|
||||
size: 12,
|
||||
color: iconColor,
|
||||
),
|
||||
child: Icon(iconData, size: 12, color: iconColor),
|
||||
),
|
||||
if (!isLast)
|
||||
Container(
|
||||
@@ -426,9 +425,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
@@ -436,7 +433,11 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.local_shipping, color: AppColors.primaryBlue, size: 20),
|
||||
const Icon(
|
||||
Icons.local_shipping,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'Thông tin giao hàng',
|
||||
@@ -576,7 +577,9 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: valueColor != null ? FontWeight.w600 : FontWeight.w500,
|
||||
fontWeight: valueColor != null
|
||||
? FontWeight.w600
|
||||
: FontWeight.w500,
|
||||
color: valueColor ?? AppColors.grey900,
|
||||
),
|
||||
),
|
||||
@@ -595,9 +598,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
@@ -605,7 +606,11 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.person_outline, color: AppColors.primaryBlue, size: 20),
|
||||
const Icon(
|
||||
Icons.person_outline,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'Thông tin khách hàng',
|
||||
@@ -634,13 +639,13 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
children: [
|
||||
const Text(
|
||||
'Loại khách hàng:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFFFFD700), Color(0xFFFFA500)],
|
||||
@@ -673,10 +678,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
@@ -714,9 +716,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
@@ -724,7 +724,11 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.inventory_2, color: AppColors.primaryBlue, size: 20),
|
||||
const Icon(
|
||||
Icons.inventory_2,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'Sản phẩm đặt hàng',
|
||||
@@ -739,113 +743,115 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
...products.map((product) => Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Product Image
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grey50,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
...products.map(
|
||||
(product) => Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Product Image
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grey50,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.image,
|
||||
color: AppColors.grey500,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.image,
|
||||
color: AppColors.grey500,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 12),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Product Info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
product['name']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Kích thước: ${product['size']}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'SKU: ${product['sku']}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Số lượng:',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
product['quantity']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
// Product Info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
product['name']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
product['unitPrice']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
product['totalPrice']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.danger,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Kích thước: ${product['size']}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'SKU: ${product['sku']}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Số lượng:',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
product['quantity']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
product['unitPrice']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
product['totalPrice']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.danger,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -870,9 +876,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
@@ -880,7 +884,11 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.receipt_long, color: AppColors.primaryBlue, size: 20),
|
||||
const Icon(
|
||||
Icons.receipt_long,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'Tổng kết đơn hàng',
|
||||
@@ -900,7 +908,9 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
_buildSummaryRow(
|
||||
'Phí vận chuyển:',
|
||||
shippingFee == 0 ? 'Miễn phí' : currencyFormatter.format(shippingFee),
|
||||
shippingFee == 0
|
||||
? 'Miễn phí'
|
||||
: currencyFormatter.format(shippingFee),
|
||||
valueColor: shippingFee == 0 ? AppColors.success : null,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -924,14 +934,15 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
// Payment Method
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.credit_card, size: 16, color: AppColors.grey500),
|
||||
const Icon(
|
||||
Icons.credit_card,
|
||||
size: 16,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Text(
|
||||
'Phương thức thanh toán:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -955,10 +966,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
const SizedBox(width: 6),
|
||||
const Text(
|
||||
'Ghi chú đơn hàng:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -980,7 +988,12 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
}
|
||||
|
||||
/// Build Summary Row
|
||||
Widget _buildSummaryRow(String label, String value, {bool isTotal = false, Color? valueColor}) {
|
||||
Widget _buildSummaryRow(
|
||||
String label,
|
||||
String value, {
|
||||
bool isTotal = false,
|
||||
Color? valueColor,
|
||||
}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -997,7 +1010,8 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
style: TextStyle(
|
||||
fontSize: isTotal ? 16 : 14,
|
||||
fontWeight: isTotal ? FontWeight.w700 : FontWeight.w500,
|
||||
color: valueColor ?? (isTotal ? AppColors.danger : AppColors.grey900),
|
||||
color:
|
||||
valueColor ?? (isTotal ? AppColors.danger : AppColors.grey900),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -1039,7 +1053,8 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
'deliveryMethod': 'Giao hàng tiêu chuẩn',
|
||||
'warehouseDate': DateTime(2023, 8, 5),
|
||||
'deliveryDate': DateTime(2023, 8, 7),
|
||||
'deliveryAddress': '123 Đường Lê Văn Lương, Phường Tân Hưng,\nQuận 7, TP. Hồ Chí Minh',
|
||||
'deliveryAddress':
|
||||
'123 Đường Lê Văn Lương, Phường Tân Hưng,\nQuận 7, TP. Hồ Chí Minh',
|
||||
'receiverName': 'Nguyễn Văn A',
|
||||
'receiverPhone': '0901234567',
|
||||
'customerName': 'Nguyễn Văn A',
|
||||
@@ -1051,7 +1066,8 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
'discount': 129000.0,
|
||||
'total': 12771000.0,
|
||||
'paymentMethod': 'Chuyển khoản ngân hàng',
|
||||
'notes': 'Giao hàng trong giờ hành chính. Vui lòng gọi trước 30 phút khi đến giao hàng.',
|
||||
'notes':
|
||||
'Giao hàng trong giờ hành chính. Vui lòng gọi trước 30 phút khi đến giao hàng.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
|
||||
}
|
||||
|
||||
void _onSearchChanged() {
|
||||
ref.read(orderSearchQueryProvider.notifier).updateQuery(
|
||||
_searchController.text,
|
||||
);
|
||||
ref
|
||||
.read(orderSearchQueryProvider.notifier)
|
||||
.updateQuery(_searchController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -68,9 +68,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
|
||||
backgroundColor: AppColors.white,
|
||||
foregroundColor: AppColors.grey900,
|
||||
centerTitle: false,
|
||||
actions: const [
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
],
|
||||
actions: const [SizedBox(width: AppSpacing.sm)],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
@@ -87,9 +85,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
|
||||
),
|
||||
|
||||
// Filter Pills
|
||||
SliverToBoxAdapter(
|
||||
child: _buildFilterPills(selectedStatus),
|
||||
),
|
||||
SliverToBoxAdapter(child: _buildFilterPills(selectedStatus)),
|
||||
|
||||
// Orders List
|
||||
SliverPadding(
|
||||
@@ -101,18 +97,15 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
|
||||
}
|
||||
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final order = orders[index];
|
||||
return OrderCard(
|
||||
order: order,
|
||||
onTap: () {
|
||||
context.push('/orders/${order.orderId}');
|
||||
},
|
||||
);
|
||||
},
|
||||
childCount: orders.length,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final order = orders[index];
|
||||
return OrderCard(
|
||||
order: order,
|
||||
onTap: () {
|
||||
context.push('/orders/${order.orderId}');
|
||||
},
|
||||
);
|
||||
}, childCount: orders.length),
|
||||
);
|
||||
},
|
||||
loading: () => _buildLoadingState(),
|
||||
@@ -143,10 +136,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Mã đơn hàng',
|
||||
hintStyle: const TextStyle(
|
||||
color: AppColors.grey500,
|
||||
fontSize: 14,
|
||||
),
|
||||
hintStyle: const TextStyle(color: AppColors.grey500, fontSize: 14),
|
||||
prefixIcon: const Icon(
|
||||
Icons.search,
|
||||
color: AppColors.grey500,
|
||||
@@ -310,10 +300,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Thử tìm kiếm với từ khóa khác',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -324,9 +311,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
|
||||
/// Build loading state
|
||||
Widget _buildLoadingState() {
|
||||
return const SliverFillRemaining(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -354,10 +339,7 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
error.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -21,11 +21,8 @@ import 'package:worker/features/orders/presentation/providers/invoices_provider.
|
||||
/// - Payment history
|
||||
/// - Action buttons
|
||||
class PaymentDetailPage extends ConsumerWidget {
|
||||
const PaymentDetailPage({required this.invoiceId, super.key});
|
||||
|
||||
const PaymentDetailPage({
|
||||
required this.invoiceId,
|
||||
super.key,
|
||||
});
|
||||
/// Invoice ID
|
||||
final String invoiceId;
|
||||
|
||||
@@ -53,9 +50,9 @@ class PaymentDetailPage extends ConsumerWidget {
|
||||
icon: const Icon(Icons.share, color: Colors.black),
|
||||
onPressed: () {
|
||||
// TODO: Implement share functionality
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chia sẻ hóa đơn')),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Chia sẻ hóa đơn')));
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -134,8 +131,14 @@ class PaymentDetailPage extends ConsumerWidget {
|
||||
icon: const Icon(Icons.chat_bubble_outline),
|
||||
label: const Text('Liên hệ hỗ trợ'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
side: const BorderSide(color: AppColors.grey100, width: 2),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
horizontal: 16,
|
||||
),
|
||||
side: const BorderSide(
|
||||
color: AppColors.grey100,
|
||||
width: 2,
|
||||
),
|
||||
foregroundColor: AppColors.grey900,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
@@ -151,12 +154,15 @@ class PaymentDetailPage extends ConsumerWidget {
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: (invoice.status == InvoiceStatus.paid || invoice.isPaid)
|
||||
onPressed:
|
||||
(invoice.status == InvoiceStatus.paid || invoice.isPaid)
|
||||
? null
|
||||
: () {
|
||||
// TODO: Navigate to payment page
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Mở cổng thanh toán')),
|
||||
const SnackBar(
|
||||
content: Text('Mở cổng thanh toán'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
@@ -171,7 +177,9 @@ class PaymentDetailPage extends ConsumerWidget {
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
backgroundColor: (invoice.status == InvoiceStatus.paid || invoice.isPaid)
|
||||
backgroundColor:
|
||||
(invoice.status == InvoiceStatus.paid ||
|
||||
invoice.isPaid)
|
||||
? AppColors.success
|
||||
: AppColors.primaryBlue,
|
||||
disabledBackgroundColor: AppColors.success,
|
||||
@@ -195,11 +203,18 @@ class PaymentDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error_outline, size: 64, color: AppColors.danger),
|
||||
const Icon(
|
||||
Icons.error_outline,
|
||||
size: 64,
|
||||
color: AppColors.danger,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Không tìm thấy hóa đơn',
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
@@ -287,7 +302,9 @@ class PaymentDetailPage extends ConsumerWidget {
|
||||
'Còn lại:',
|
||||
currencyFormatter.format(amountRemaining),
|
||||
isHighlighted: true,
|
||||
valueColor: amountRemaining > 0 ? AppColors.danger : AppColors.success,
|
||||
valueColor: amountRemaining > 0
|
||||
? AppColors.danger
|
||||
: AppColors.success,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -354,7 +371,9 @@ class PaymentDetailPage extends ConsumerWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isOverdue ? AppColors.danger : AppColors.grey900,
|
||||
color: isOverdue
|
||||
? AppColors.danger
|
||||
: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -451,79 +470,84 @@ class PaymentDetailPage extends ConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
...products.map((product) => Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Product image placeholder
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grey50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
...products
|
||||
.map(
|
||||
(product) => Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Product image placeholder
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grey50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.image,
|
||||
color: AppColors.grey500,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.image,
|
||||
color: AppColors.grey500,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Product info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
product['name']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'SKU: ${product['sku']}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Số lượng: ${product['quantity']}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Product info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
product['name']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
Text(
|
||||
product['price']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'SKU: ${product['sku']}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Số lượng: ${product['quantity']}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
product['price']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
)).toList(),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -27,11 +27,7 @@ class PaymentQrPage extends HookConsumerWidget {
|
||||
final String orderId;
|
||||
final double amount;
|
||||
|
||||
const PaymentQrPage({
|
||||
super.key,
|
||||
required this.orderId,
|
||||
required this.amount,
|
||||
});
|
||||
const PaymentQrPage({super.key, required this.orderId, required this.amount});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -186,7 +182,8 @@ class PaymentQrPage extends HookConsumerWidget {
|
||||
Widget _buildQrCodeCard(double amount, String orderId) {
|
||||
// Generate QR code data URL
|
||||
final qrData = Uri.encodeComponent(
|
||||
'https://eurotile.com/payment/$orderId?amount=$amount');
|
||||
'https://eurotile.com/payment/$orderId?amount=$amount',
|
||||
);
|
||||
final qrUrl =
|
||||
'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=$qrData';
|
||||
|
||||
@@ -283,11 +280,7 @@ class PaymentQrPage extends HookConsumerWidget {
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Bank Name
|
||||
_buildInfoRow(
|
||||
context: context,
|
||||
label: 'Ngân hàng:',
|
||||
value: 'BIDV',
|
||||
),
|
||||
_buildInfoRow(context: context, label: 'Ngân hàng:', value: 'BIDV'),
|
||||
|
||||
const Divider(height: 24),
|
||||
|
||||
@@ -329,8 +322,11 @@ class PaymentQrPage extends HookConsumerWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.lightbulb_outline,
|
||||
color: AppColors.primaryBlue, size: 20),
|
||||
const Icon(
|
||||
Icons.lightbulb_outline,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: RichText(
|
||||
@@ -414,7 +410,10 @@ class PaymentQrPage extends HookConsumerWidget {
|
||||
),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.primaryBlue,
|
||||
side: const BorderSide(color: AppColors.primaryBlue, width: 1.5),
|
||||
side: const BorderSide(
|
||||
color: AppColors.primaryBlue,
|
||||
width: 1.5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.button),
|
||||
@@ -534,9 +533,7 @@ class PaymentQrPage extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('• ', style: TextStyle(fontSize: 14)),
|
||||
Expanded(
|
||||
child: Text(text, style: const TextStyle(fontSize: 14)),
|
||||
),
|
||||
Expanded(child: Text(text, style: const TextStyle(fontSize: 14))),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -597,9 +594,6 @@ class PaymentQrPage extends HookConsumerWidget {
|
||||
|
||||
/// Format currency
|
||||
String _formatCurrency(double amount) {
|
||||
return '${amount.toStringAsFixed(0).replaceAllMapped(
|
||||
RegExp(r'(\d)(?=(\d{3})+(?!\d))'),
|
||||
(Match m) => '${m[1]}.',
|
||||
)}₫';
|
||||
return '${amount.toStringAsFixed(0).replaceAllMapped(RegExp(r'(\d)(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]}.')}₫';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,9 @@ class _PaymentsPageState extends ConsumerState<PaymentsPage>
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController..removeListener(_onTabChanged)
|
||||
..dispose();
|
||||
_tabController
|
||||
..removeListener(_onTabChanged)
|
||||
..dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -57,26 +58,38 @@ class _PaymentsPageState extends ConsumerState<PaymentsPage>
|
||||
}
|
||||
|
||||
/// Filter invoices based on tab key
|
||||
List<InvoiceModel> _filterInvoices(List<InvoiceModel> invoices, String tabKey) {
|
||||
List<InvoiceModel> _filterInvoices(
|
||||
List<InvoiceModel> invoices,
|
||||
String tabKey,
|
||||
) {
|
||||
var filtered = List<InvoiceModel>.from(invoices);
|
||||
|
||||
switch (tabKey) {
|
||||
case 'unpaid':
|
||||
// Unpaid tab: issued status only
|
||||
filtered = filtered
|
||||
.where((invoice) => invoice.status == InvoiceStatus.issued && !invoice.isPaid)
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.status == InvoiceStatus.issued && !invoice.isPaid,
|
||||
)
|
||||
.toList();
|
||||
break;
|
||||
case 'overdue':
|
||||
// Overdue tab: overdue status
|
||||
filtered = filtered
|
||||
.where((invoice) => invoice.status == InvoiceStatus.overdue || invoice.isOverdue)
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.status == InvoiceStatus.overdue || invoice.isOverdue,
|
||||
)
|
||||
.toList();
|
||||
break;
|
||||
case 'paid':
|
||||
// Paid tab: paid status
|
||||
filtered = filtered
|
||||
.where((invoice) => invoice.status == InvoiceStatus.paid || invoice.isPaid)
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.status == InvoiceStatus.paid || invoice.isPaid,
|
||||
)
|
||||
.toList();
|
||||
break;
|
||||
case 'all':
|
||||
@@ -96,13 +109,21 @@ class _PaymentsPageState extends ConsumerState<PaymentsPage>
|
||||
return {
|
||||
'all': invoices.length,
|
||||
'unpaid': invoices
|
||||
.where((invoice) => invoice.status == InvoiceStatus.issued && !invoice.isPaid)
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.status == InvoiceStatus.issued && !invoice.isPaid,
|
||||
)
|
||||
.length,
|
||||
'overdue': invoices
|
||||
.where((invoice) => invoice.status == InvoiceStatus.overdue || invoice.isOverdue)
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.status == InvoiceStatus.overdue || invoice.isOverdue,
|
||||
)
|
||||
.length,
|
||||
'paid': invoices
|
||||
.where((invoice) => invoice.status == InvoiceStatus.paid || invoice.isPaid)
|
||||
.where(
|
||||
(invoice) => invoice.status == InvoiceStatus.paid || invoice.isPaid,
|
||||
)
|
||||
.length,
|
||||
};
|
||||
}
|
||||
@@ -190,7 +211,10 @@ class _PaymentsPageState extends ConsumerState<PaymentsPage>
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: _tabs.map((tab) {
|
||||
final filteredInvoices = _filterInvoices(allInvoices, tab['key']!);
|
||||
final filteredInvoices = _filterInvoices(
|
||||
allInvoices,
|
||||
tab['key']!,
|
||||
);
|
||||
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
@@ -200,21 +224,25 @@ class _PaymentsPageState extends ConsumerState<PaymentsPage>
|
||||
sliver: filteredInvoices.isEmpty
|
||||
? _buildEmptyState(tab['label']!)
|
||||
: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final invoice = filteredInvoices[index];
|
||||
return InvoiceCard(
|
||||
invoice: invoice,
|
||||
onTap: () {
|
||||
context.push('/payments/${invoice.invoiceId}');
|
||||
},
|
||||
onPaymentTap: () {
|
||||
context.push('/payments/${invoice.invoiceId}');
|
||||
},
|
||||
);
|
||||
},
|
||||
childCount: filteredInvoices.length,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((
|
||||
context,
|
||||
index,
|
||||
) {
|
||||
final invoice = filteredInvoices[index];
|
||||
return InvoiceCard(
|
||||
invoice: invoice,
|
||||
onTap: () {
|
||||
context.push(
|
||||
'/payments/${invoice.invoiceId}',
|
||||
);
|
||||
},
|
||||
onPaymentTap: () {
|
||||
context.push(
|
||||
'/payments/${invoice.invoiceId}',
|
||||
);
|
||||
},
|
||||
);
|
||||
}, childCount: filteredInvoices.length),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -240,9 +268,7 @@ class _PaymentsPageState extends ConsumerState<PaymentsPage>
|
||||
foregroundColor: AppColors.grey900,
|
||||
centerTitle: false,
|
||||
),
|
||||
body: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
body: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error: (error, stack) => Scaffold(
|
||||
backgroundColor: const Color(0xFFF4F6F8),
|
||||
@@ -281,10 +307,7 @@ class _PaymentsPageState extends ConsumerState<PaymentsPage>
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
error.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
@@ -339,10 +362,7 @@ class _PaymentsPageState extends ConsumerState<PaymentsPage>
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Kéo xuống để làm mới',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -74,17 +74,27 @@ Future<List<InvoiceModel>> filteredInvoices(Ref ref) async {
|
||||
if (selectedStatus == 'unpaid') {
|
||||
// Unpaid tab: issued status only
|
||||
filtered = filtered
|
||||
.where((invoice) => invoice.status == InvoiceStatus.issued && !invoice.isPaid)
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.status == InvoiceStatus.issued && !invoice.isPaid,
|
||||
)
|
||||
.toList();
|
||||
} else if (selectedStatus == 'overdue') {
|
||||
// Overdue tab: overdue status
|
||||
filtered = filtered
|
||||
.where((invoice) => invoice.status == InvoiceStatus.overdue || invoice.isOverdue)
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.status == InvoiceStatus.overdue ||
|
||||
invoice.isOverdue,
|
||||
)
|
||||
.toList();
|
||||
} else if (selectedStatus == 'paid') {
|
||||
// Paid tab: paid status
|
||||
filtered = filtered
|
||||
.where((invoice) => invoice.status == InvoiceStatus.paid || invoice.isPaid)
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.status == InvoiceStatus.paid || invoice.isPaid,
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -115,17 +125,25 @@ Future<Map<String, int>> invoicesCountByStatus(Ref ref) async {
|
||||
|
||||
// Unpaid tab (issued status)
|
||||
counts['unpaid'] = invoices
|
||||
.where((invoice) => invoice.status == InvoiceStatus.issued && !invoice.isPaid)
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.status == InvoiceStatus.issued && !invoice.isPaid,
|
||||
)
|
||||
.length;
|
||||
|
||||
// Overdue tab
|
||||
counts['overdue'] = invoices
|
||||
.where((invoice) => invoice.status == InvoiceStatus.overdue || invoice.isOverdue)
|
||||
.where(
|
||||
(invoice) =>
|
||||
invoice.status == InvoiceStatus.overdue || invoice.isOverdue,
|
||||
)
|
||||
.length;
|
||||
|
||||
// Paid tab
|
||||
counts['paid'] = invoices
|
||||
.where((invoice) => invoice.status == InvoiceStatus.paid || invoice.isPaid)
|
||||
.where(
|
||||
(invoice) => invoice.status == InvoiceStatus.paid || invoice.isPaid,
|
||||
)
|
||||
.length;
|
||||
|
||||
return counts;
|
||||
@@ -144,7 +162,10 @@ Future<double> totalInvoicesAmount(Ref ref) async {
|
||||
|
||||
return invoicesAsync.when(
|
||||
data: (invoices) {
|
||||
return invoices.fold<double>(0.0, (sum, invoice) => sum + invoice.totalAmount);
|
||||
return invoices.fold<double>(
|
||||
0.0,
|
||||
(sum, invoice) => sum + invoice.totalAmount,
|
||||
);
|
||||
},
|
||||
loading: () => 0.0,
|
||||
error: (error, stack) => 0.0,
|
||||
@@ -160,7 +181,10 @@ Future<double> totalUnpaidAmount(Ref ref) async {
|
||||
|
||||
return invoicesAsync.when(
|
||||
data: (invoices) {
|
||||
return invoices.fold<double>(0.0, (sum, invoice) => sum + invoice.amountRemaining);
|
||||
return invoices.fold<double>(
|
||||
0.0,
|
||||
(sum, invoice) => sum + invoice.amountRemaining,
|
||||
);
|
||||
},
|
||||
loading: () => 0.0,
|
||||
error: (error, stack) => 0.0,
|
||||
|
||||
@@ -102,9 +102,9 @@ Future<List<OrderModel>> filteredOrders(Ref ref) async {
|
||||
if (searchQuery.isNotEmpty) {
|
||||
filtered = filtered
|
||||
.where(
|
||||
(order) => order.orderNumber
|
||||
.toLowerCase()
|
||||
.contains(searchQuery.toLowerCase()),
|
||||
(order) => order.orderNumber.toLowerCase().contains(
|
||||
searchQuery.toLowerCase(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -40,9 +40,7 @@ class InvoiceCard extends StatelessWidget {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
@@ -89,16 +87,10 @@ class InvoiceCard extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Invoice dates
|
||||
_buildDetailRow(
|
||||
'Ngày hóa đơn:',
|
||||
_formatDate(invoice.issueDate),
|
||||
),
|
||||
_buildDetailRow('Ngày hóa đơn:', _formatDate(invoice.issueDate)),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
_buildDetailRow(
|
||||
'Hạn thanh toán:',
|
||||
_formatDate(invoice.dueDate),
|
||||
),
|
||||
_buildDetailRow('Hạn thanh toán:', _formatDate(invoice.dueDate)),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -161,10 +153,7 @@ class InvoiceCard extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
|
||||
Text(
|
||||
@@ -191,10 +180,7 @@ class InvoiceCard extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: const TextStyle(fontSize: 13, color: AppColors.grey500),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
@@ -211,10 +197,7 @@ class InvoiceCard extends StatelessWidget {
|
||||
/// Build status badge
|
||||
Widget _buildStatusBadge() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor(invoice.status).withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -250,9 +233,7 @@ class InvoiceCard extends StatelessWidget {
|
||||
foregroundColor: Colors.white,
|
||||
disabledForegroundColor: AppColors.grey500,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
elevation: 0,
|
||||
),
|
||||
child: Row(
|
||||
@@ -263,10 +244,7 @@ class InvoiceCard extends StatelessWidget {
|
||||
Icon(Icons.credit_card),
|
||||
Text(
|
||||
buttonText,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -22,11 +22,7 @@ class OrderCard extends StatelessWidget {
|
||||
/// Tap callback
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const OrderCard({
|
||||
required this.order,
|
||||
this.onTap,
|
||||
super.key,
|
||||
});
|
||||
const OrderCard({required this.order, this.onTap, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -39,9 +35,7 @@ class OrderCard extends StatelessWidget {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
@@ -80,10 +74,7 @@ class OrderCard extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Order details
|
||||
_buildDetailRow(
|
||||
'Ngày đặt:',
|
||||
_formatDate(order.createdAt),
|
||||
),
|
||||
_buildDetailRow('Ngày đặt:', _formatDate(order.createdAt)),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
_buildDetailRow(
|
||||
@@ -94,10 +85,7 @@ class OrderCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
_buildDetailRow(
|
||||
'Địa chỉ:',
|
||||
_getShortAddress(),
|
||||
),
|
||||
_buildDetailRow('Địa chỉ:', _getShortAddress()),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Status badge
|
||||
@@ -116,19 +104,13 @@ class OrderCard extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey900),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -138,10 +120,7 @@ class OrderCard extends StatelessWidget {
|
||||
/// Build status badge
|
||||
Widget _buildStatusBadge() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor(order.status).withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
|
||||
Reference in New Issue
Block a user