fix loyalty

This commit is contained in:
Phuoc Nguyen
2025-10-27 16:06:31 +07:00
parent 8eae79f72b
commit c0527a086c
8 changed files with 710 additions and 6 deletions

View File

@@ -308,7 +308,7 @@ class LoyaltyPage extends ConsumerWidget {
'icon': Icons.history,
'title': 'Lịch sử điểm',
'subtitle': 'Xem chi tiết cộng/trừ điểm',
'route': null,
'route': '/loyalty/points-history',
},
{
'icon': Icons.person_add,

View File

@@ -0,0 +1,329 @@
/// Page: Points History Page
///
/// Displays history of points earned and spent transactions.
library;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/database/models/enums.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/loyalty/data/datasources/points_history_local_datasource.dart';
import 'package:worker/features/loyalty/data/models/loyalty_point_entry_model.dart';
import 'package:worker/features/loyalty/presentation/providers/points_history_provider.dart';
/// Points History Page
///
/// Features:
/// - Filter section with date range
/// - List of transaction cards
/// - Each card shows: description, date, amount, points change, new balance
/// - Complaint button for each transaction
class PointsHistoryPage extends ConsumerWidget {
const PointsHistoryPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final historyAsync = ref.watch(pointsHistoryProvider);
return Scaffold(
backgroundColor: const Color(0xFFF4F6F8),
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => context.pop(),
),
title: const Text(
'Lịch sử điểm',
style: TextStyle(color: Colors.black),
),
elevation: AppBarSpecs.elevation,
backgroundColor: AppColors.white,
foregroundColor: AppColors.grey900,
centerTitle: false,
),
body: RefreshIndicator(
onRefresh: () async {
await ref.read(pointsHistoryProvider.notifier).refresh();
},
child: historyAsync.when(
data: (entries) {
if (entries.isEmpty) {
return _buildEmptyState();
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Filter Section
_buildFilterSection(),
const SizedBox(height: 16),
// Transaction List
...entries.map((entry) => _buildTransactionCard(context, ref, entry)),
],
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => _buildErrorState(error),
),
),
);
}
/// Build filter section
Widget _buildFilterSection() {
return Card(
elevation: 1,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Bộ lọc',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
),
),
Icon(
Icons.filter_list,
color: AppColors.primaryBlue,
size: 20,
),
],
),
const SizedBox(height: 8),
const Text(
'Thời gian hiệu lực: 01/01/2023 - 31/12/2023',
style: TextStyle(
fontSize: 12,
color: AppColors.grey500,
),
),
],
),
),
);
}
/// Build transaction card
Widget _buildTransactionCard(
BuildContext context,
WidgetRef ref,
LoyaltyPointEntryModel entry,
) {
final dateFormatter = DateFormat('dd/MM/yyyy HH:mm:ss');
final currencyFormatter = NumberFormat.currency(
locale: 'vi_VN',
symbol: 'VND',
decimalDigits: 0,
);
// Get transaction amount if it's a purchase
final datasource = ref.read(pointsHistoryLocalDataSourceProvider);
final transactionAmount = datasource.getTransactionAmount(entry.description);
return Card(
elevation: 1,
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Top row: Description and Complaint button
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Description
Text(
entry.description,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: AppColors.primaryBlue,
),
),
const SizedBox(height: 4),
// Timestamp
Text(
'Thời gian: ${dateFormatter.format(entry.timestamp)}',
style: const TextStyle(
fontSize: 12,
color: AppColors.grey500,
),
),
// Transaction amount (if purchase)
if (transactionAmount != null) ...[
const SizedBox(height: 2),
Text(
'Giao dịch: ${currencyFormatter.format(transactionAmount)}',
style: const TextStyle(
fontSize: 12,
color: AppColors.grey500,
),
),
],
],
),
),
const SizedBox(width: 12),
// Complaint button
OutlinedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Chức năng khiếu nại đang phát triển')),
);
},
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
side: const BorderSide(color: AppColors.grey500),
foregroundColor: AppColors.grey900,
textStyle: const TextStyle(fontSize: 12),
minimumSize: const Size(0, 32),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
),
child: const Text('Khiếu nại'),
),
],
),
const SizedBox(height: 12),
// Bottom row: Points change and new balance
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// Points change
Text(
entry.points > 0 ? '+${entry.points}' : '${entry.points}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: entry.points > 0
? AppColors.success
: entry.points < 0
? AppColors.danger
: AppColors.grey900,
),
),
const SizedBox(height: 2),
// New balance
Text(
'Điểm mới: ${entry.balanceAfter}',
style: const TextStyle(
fontSize: 12,
color: AppColors.primaryBlue,
),
),
],
),
],
),
],
),
),
);
}
/// Build empty state
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.history,
size: 80,
color: AppColors.grey500.withValues(alpha: 0.5),
),
const SizedBox(height: 16),
const Text(
'Chưa có lịch sử điểm',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.grey500,
),
),
const SizedBox(height: 8),
const Text(
'Kéo xuống để làm mới',
style: TextStyle(
fontSize: 14,
color: AppColors.grey500,
),
),
],
),
);
}
/// Build error state
Widget _buildErrorState(Object error) {
print(error.toString());
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 80,
color: AppColors.danger.withValues(alpha: 0.7),
),
const SizedBox(height: 16),
const Text(
'Có lỗi xảy ra',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.grey900,
),
),
const SizedBox(height: 8),
Text(
error.toString(),
style: const TextStyle(
fontSize: 14,
color: AppColors.grey500,
),
textAlign: TextAlign.center,
),
],
),
);
}
}