add auth, format
This commit is contained in:
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user