add auth, format

This commit is contained in:
Phuoc Nguyen
2025-11-07 11:52:06 +07:00
parent 24a8508fce
commit 3803bd26e0
173 changed files with 8505 additions and 7116 deletions

View File

@@ -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.',
};
}
}

View File

@@ -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,
),
],

View File

@@ -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(),
],
),
),

View File

@@ -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]}.')}';
}
}

View File

@@ -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),
),
],
),