fix order, update qr

This commit is contained in:
Phuoc Nguyen
2025-12-01 15:28:07 +07:00
parent 250c453413
commit e62c466155
7 changed files with 56 additions and 205 deletions

View File

@@ -350,14 +350,9 @@ final routerProvider = Provider<GoRouter>((ref) {
name: RouteNames.paymentQr, name: RouteNames.paymentQr,
pageBuilder: (context, state) { pageBuilder: (context, state) {
final orderId = state.uri.queryParameters['orderId'] ?? ''; final orderId = state.uri.queryParameters['orderId'] ?? '';
final amountStr = state.uri.queryParameters['amount'] ?? '0';
final amount = double.tryParse(amountStr) ?? 0.0;
return MaterialPage( return MaterialPage(
key: state.pageKey, key: state.pageKey,
child: PaymentQrPage( child: PaymentQrPage(orderId: orderId),
orderId: orderId,
amount: amount,
),
); );
}, },
), ),

View File

@@ -14,10 +14,11 @@ class AppTheme {
/// Light theme configuration /// Light theme configuration
/// [seedColor] - Optional custom seed color, defaults to AppColors.defaultSeedColor /// [seedColor] - Optional custom seed color, defaults to AppColors.defaultSeedColor
static ThemeData lightTheme([Color? seedColor]) { static ThemeData lightTheme([Color? seedColor]) {
final seed = seedColor ?? AppColors.defaultSeedColor;
final ColorScheme colorScheme = ColorScheme.fromSeed( final ColorScheme colorScheme = ColorScheme.fromSeed(
seedColor: seedColor ?? AppColors.defaultSeedColor, seedColor: seed,
brightness: Brightness.light, brightness: Brightness.light,
); ).copyWith(primary: seed);
return ThemeData( return ThemeData(
useMaterial3: true, useMaterial3: true,
@@ -239,10 +240,11 @@ class AppTheme {
/// Dark theme configuration /// Dark theme configuration
/// [seedColor] - Optional custom seed color, defaults to AppColors.defaultSeedColor /// [seedColor] - Optional custom seed color, defaults to AppColors.defaultSeedColor
static ThemeData darkTheme([Color? seedColor]) { static ThemeData darkTheme([Color? seedColor]) {
final seed = seedColor ?? AppColors.defaultSeedColor;
final ColorScheme colorScheme = ColorScheme.fromSeed( final ColorScheme colorScheme = ColorScheme.fromSeed(
seedColor: seedColor ?? AppColors.defaultSeedColor, seedColor: seed,
brightness: Brightness.dark, brightness: Brightness.dark,
); ).copyWith(primary: seed);
return ThemeData( return ThemeData(
useMaterial3: true, useMaterial3: true,

View File

@@ -11,6 +11,7 @@ import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/enums/status_color.dart'; import 'package:worker/core/enums/status_color.dart';
import 'package:worker/core/router/app_router.dart';
import 'package:worker/core/theme/colors.dart'; import 'package:worker/core/theme/colors.dart';
import 'package:worker/core/utils/extensions.dart'; import 'package:worker/core/utils/extensions.dart';
import 'package:worker/features/account/domain/entities/address.dart'; import 'package:worker/features/account/domain/entities/address.dart';
@@ -38,8 +39,8 @@ class OrderDetailPage extends ConsumerWidget {
onPopInvoked: (didPop) { onPopInvoked: (didPop) {
if (didPop) { if (didPop) {
// Dispose providers when leaving the page // Dispose providers when leaving the page
ref.invalidate(updateOrderAddressProvider); ref..invalidate(updateOrderAddressProvider)
ref.invalidate(cancelOrderProvider); ..invalidate(cancelOrderProvider);
} }
}, },
child: Scaffold( child: Scaffold(
@@ -1411,11 +1412,9 @@ class OrderDetailPage extends ConsumerWidget {
Expanded( Expanded(
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
// TODO: Navigate to payment page // Navigate to payment QR page
ScaffoldMessenger.of(context).showSnackBar( context.push(
const SnackBar( '${RouteNames.paymentQr}?orderId=${orderDetail.order.name}',
content: Text('Chức năng thanh toán đang phát triển'),
),
); );
}, },
icon: const FaIcon(FontAwesomeIcons.creditCard, size: 18), icon: const FaIcon(FontAwesomeIcons.creditCard, size: 18),

View File

@@ -37,8 +37,8 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
@override @override
void dispose() { void dispose() {
_searchController.removeListener(_onSearchChanged); _searchController..removeListener(_onSearchChanged)
_searchController.dispose(); ..dispose();
super.dispose(); super.dispose();
} }
@@ -50,8 +50,9 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final filteredOrdersAsync = ref.watch(filteredOrdersProvider); final ordersAsync = ref.watch(ordersProvider);
final selectedStatus = ref.watch(selectedOrderStatusProvider); final selectedStatus = ref.watch(selectedOrderStatusProvider);
final searchQuery = ref.watch(orderSearchQueryProvider);
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF4F6F8), backgroundColor: const Color(0xFFF4F6F8),
@@ -102,22 +103,49 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
// Orders List // Orders List
SliverPadding( SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
sliver: filteredOrdersAsync.when( sliver: ordersAsync.when(
data: (orders) { data: (orders) {
if (orders.isEmpty) { // Filter by status
return _buildEmptyState(); var filtered = orders;
if (selectedStatus != null) {
filtered = filtered
.where((order) => order.status == selectedStatus)
.toList();
} }
// Filter by search query
if (searchQuery.isNotEmpty) {
filtered = filtered
.where((order) => order.name
.toLowerCase()
.contains(searchQuery.toLowerCase()))
.toList();
}
// Sort by transaction date (newest first)
filtered.sort((a, b) {
try {
final aDate = DateTime.parse(a.transactionDate);
final bDate = DateTime.parse(b.transactionDate);
return bDate.compareTo(aDate);
} catch (e) {
return 0;
}
});
if (filtered.isEmpty) {
return _buildEmptyState();
}
return SliverList( return SliverList(
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
final order = orders[index]; final order = filtered[index];
return OrderCard( return OrderCard(
order: order, order: order,
onTap: () { onTap: () {
context.push('/orders/${order.name}'); context.push('/orders/${order.name}');
}, },
); );
}, childCount: orders.length), }, childCount: filtered.length),
); );
}, },
loading: () => _buildLoadingState(), loading: () => _buildLoadingState(),
@@ -149,11 +177,14 @@ class _OrdersPageState extends ConsumerState<OrdersPage> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Mã đơn hàng', hintText: 'Mã đơn hàng',
hintStyle: const TextStyle(color: AppColors.grey500, fontSize: 14), hintStyle: const TextStyle(color: AppColors.grey500, fontSize: 14),
prefixIcon: const FaIcon( prefixIcon: const Padding(
padding: EdgeInsets.all(14),
child: FaIcon(
FontAwesomeIcons.magnifyingGlass, FontAwesomeIcons.magnifyingGlass,
color: AppColors.grey500, color: AppColors.grey500,
size: 18, size: 18,
), ),
),
suffixIcon: _searchController.text.isNotEmpty suffixIcon: _searchController.text.isNotEmpty
? IconButton( ? IconButton(
icon: const FaIcon( icon: const FaIcon(

View File

@@ -2,7 +2,6 @@
/// ///
/// QR code payment screen with bank transfer information. /// QR code payment screen with bank transfer information.
/// Features: /// Features:
/// - Payment amount display with minimum payment warning
/// - QR code for quick payment /// - QR code for quick payment
/// - Bank transfer information with copy buttons /// - Bank transfer information with copy buttons
/// - Payment confirmation and proof upload buttons /// - Payment confirmation and proof upload buttons
@@ -31,12 +30,10 @@ import 'package:worker/features/orders/presentation/providers/order_repository_p
/// Displays QR code and bank transfer information for payment. /// Displays QR code and bank transfer information for payment.
class PaymentQrPage extends HookConsumerWidget { class PaymentQrPage extends HookConsumerWidget {
final String orderId; final String orderId;
final double amount;
const PaymentQrPage({ const PaymentQrPage({
super.key, super.key,
required this.orderId, required this.orderId,
required this.amount,
}); });
@override @override
@@ -126,14 +123,8 @@ class PaymentQrPage extends HookConsumerWidget {
children: [ children: [
const SizedBox(height: AppSpacing.md), const SizedBox(height: AppSpacing.md),
// Payment Amount Card
_buildAmountCard(amount),
const SizedBox(height: AppSpacing.md),
// QR Code Card // QR Code Card
_buildQrCodeCard( _buildQrCodeCard(
amount,
orderId, orderId,
qrCodeData.value?['qr_code'] as String?, qrCodeData.value?['qr_code'] as String?,
isLoadingQr.value, isLoadingQr.value,
@@ -187,70 +178,8 @@ class PaymentQrPage extends HookConsumerWidget {
); );
} }
/// Build payment amount card
Widget _buildAmountCard(double amount) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(AppRadius.card),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Text(
_formatCurrency(amount),
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColors.primaryBlue,
),
),
const SizedBox(height: 8),
const Text(
'Số tiền cần thanh toán',
style: TextStyle(fontSize: 14, color: AppColors.grey500),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFFFF8E1),
borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all(color: const Color(0xFFFFD54F)),
),
child: Row(
children: [
const FaIcon(FontAwesomeIcons.circleInfo, color: AppColors.warning, size: 18),
const SizedBox(width: 8),
Expanded(
child: Text(
'Thanh toán không dưới 20%',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.orange[900],
),
),
),
],
),
),
],
),
);
}
/// Build QR code card /// Build QR code card
Widget _buildQrCodeCard( Widget _buildQrCodeCard(
double amount,
String orderId, String orderId,
String? qrCodeData, String? qrCodeData,
bool isLoading, bool isLoading,
@@ -956,7 +885,6 @@ class PaymentQrPage extends HookConsumerWidget {
RouteNames.orderSuccess, RouteNames.orderSuccess,
queryParameters: { queryParameters: {
'orderNumber': orderId, 'orderNumber': orderId,
'total': amount.toString(),
'isNegotiation': 'false', 'isNegotiation': 'false',
}, },
); );
@@ -985,9 +913,4 @@ 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]}.')}';
}
} }

View File

@@ -91,55 +91,6 @@ class OrderSearchQuery extends _$OrderSearchQuery {
} }
} }
/// Filtered Orders Provider
///
/// Filters orders by selected status and search query.
@riverpod
Future<List<Order>> filteredOrders(Ref ref) async {
final ordersAsync = ref.watch(ordersProvider);
final selectedStatus = ref.watch(selectedOrderStatusProvider);
final searchQuery = ref.watch(orderSearchQueryProvider);
return ordersAsync.when(
data: (orders) {
var filtered = orders;
// Filter by status
if (selectedStatus != null) {
filtered = filtered
.where((order) => order.status == selectedStatus)
.toList();
}
// Filter by search query
if (searchQuery.isNotEmpty) {
filtered = filtered
.where(
(order) => order.name.toLowerCase().contains(
searchQuery.toLowerCase(),
),
)
.toList();
}
// Sort by transaction date (newest first)
filtered.sort((a, b) {
try {
final aDate = DateTime.parse(a.transactionDate);
final bDate = DateTime.parse(b.transactionDate);
return bDate.compareTo(aDate);
} catch (e) {
return 0; // Keep original order if parsing fails
}
});
return filtered;
},
loading: () => [],
error: (error, stack) => [],
);
}
/// Orders Count by Status Provider /// Orders Count by Status Provider
/// ///
/// Returns count of orders for each status. /// Returns count of orders for each status.

View File

@@ -205,56 +205,6 @@ abstract class _$OrderSearchQuery extends $Notifier<String> {
} }
} }
/// Filtered Orders Provider
///
/// Filters orders by selected status and search query.
@ProviderFor(filteredOrders)
const filteredOrdersProvider = FilteredOrdersProvider._();
/// Filtered Orders Provider
///
/// Filters orders by selected status and search query.
final class FilteredOrdersProvider
extends
$FunctionalProvider<
AsyncValue<List<Order>>,
List<Order>,
FutureOr<List<Order>>
>
with $FutureModifier<List<Order>>, $FutureProvider<List<Order>> {
/// Filtered Orders Provider
///
/// Filters orders by selected status and search query.
const FilteredOrdersProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'filteredOrdersProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$filteredOrdersHash();
@$internal
@override
$FutureProviderElement<List<Order>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<List<Order>> create(Ref ref) {
return filteredOrders(ref);
}
}
String _$filteredOrdersHash() => r'04c5c87d7138b66987c8b45f878d445026ec8e19';
/// Orders Count by Status Provider /// Orders Count by Status Provider
/// ///
/// Returns count of orders for each status. /// Returns count of orders for each status.