This commit is contained in:
Phuoc Nguyen
2025-12-05 10:11:03 +07:00
parent b9b6d91a87
commit e0a9b3b9f4
5 changed files with 455 additions and 47 deletions

View File

@@ -17,7 +17,7 @@ import 'package:worker/features/loyalty/presentation/providers/points_history_pr
/// Points History Page
///
/// Features:
/// - Filter section with date range
/// - Filter section with date range picker
/// - List of transaction cards with new design
/// - Each card shows: code, date, description, reference, points change, balance after
class PointsHistoryPage extends ConsumerWidget {
@@ -26,7 +26,11 @@ class PointsHistoryPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
// Use unfiltered data for now (mock data)
final historyAsync = ref.watch(pointsHistoryProvider);
final filter = ref.watch(pointsHistoryFilterProvider);
//todo: implement filtering logic in provider later
//:note: final historyAsync = ref.watch(filteredPointsHistoryProvider);
return Scaffold(
backgroundColor: colorScheme.surfaceContainerLowest,
@@ -50,22 +54,21 @@ class PointsHistoryPage extends ConsumerWidget {
},
child: historyAsync.when(
data: (entries) {
if (entries.isEmpty) {
return _buildEmptyState(colorScheme);
}
return ListView(
padding: const EdgeInsets.all(16),
children: [
// Filter Section
_buildFilterSection(colorScheme),
_buildFilterSection(context, ref, colorScheme, filter),
const SizedBox(height: 16),
// Transaction List
...entries.map(
(entry) => _buildTransactionCard(context, entry, colorScheme),
),
// Empty state or Transaction List
if (entries.isEmpty)
_buildEmptyStateInline(colorScheme)
else
...entries.map(
(entry) => _buildTransactionCard(context, entry, colorScheme),
),
],
);
},
@@ -76,19 +79,26 @@ class PointsHistoryPage extends ConsumerWidget {
);
}
/// Build filter section
Widget _buildFilterSection(ColorScheme colorScheme) {
/// Build filter section with date pickers
Widget _buildFilterSection(
BuildContext context,
WidgetRef ref,
ColorScheme colorScheme,
PointsHistoryFilter filter,
) {
final dateFormatter = DateFormat('dd/MM/yyyy');
return Card(
elevation: 1,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Bộ lọc',
@@ -98,20 +108,216 @@ class PointsHistoryPage extends ConsumerWidget {
color: colorScheme.onSurface,
),
),
const SizedBox(height: 4),
Text(
'Thời gian hiệu lực: 01/01/2023 - 31/12/2023',
style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
FaIcon(FontAwesomeIcons.filter, color: colorScheme.primary, size: 18),
],
),
const SizedBox(height: 16),
// Date range row
Row(
children: [
// Start date
Expanded(
child: _buildDateField(
context: context,
colorScheme: colorScheme,
label: 'Từ ngày',
value: filter.startDate,
dateFormatter: dateFormatter,
onTap: () async {
final date = await showDatePicker(
context: context,
initialDate: filter.startDate ?? DateTime.now(),
firstDate: DateTime(2020),
lastDate: filter.endDate ?? DateTime.now(),
);
if (date != null) {
ref.read(pointsHistoryFilterProvider.notifier).setStartDate(date);
}
},
),
),
const SizedBox(width: 12),
// End date
Expanded(
child: _buildDateField(
context: context,
colorScheme: colorScheme,
label: 'Đến ngày',
value: filter.endDate,
dateFormatter: dateFormatter,
onTap: () async {
final date = await showDatePicker(
context: context,
initialDate: filter.endDate ?? DateTime.now(),
firstDate: filter.startDate ?? DateTime(2020),
lastDate: DateTime.now(),
);
if (date != null) {
ref.read(pointsHistoryFilterProvider.notifier).setEndDate(date);
}
},
),
),
],
),
FaIcon(FontAwesomeIcons.filter, color: colorScheme.primary, size: 18),
const SizedBox(height: 12),
// Quick filter chips
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildQuickFilterChip(
context: context,
ref: ref,
colorScheme: colorScheme,
label: 'Hôm nay',
onTap: () {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
ref.read(pointsHistoryFilterProvider.notifier).setDateRange(today, today);
},
),
const SizedBox(width: 8),
_buildQuickFilterChip(
context: context,
ref: ref,
colorScheme: colorScheme,
label: '7 ngày',
onTap: () {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final weekAgo = today.subtract(const Duration(days: 7));
ref.read(pointsHistoryFilterProvider.notifier).setDateRange(weekAgo, today);
},
),
const SizedBox(width: 8),
_buildQuickFilterChip(
context: context,
ref: ref,
colorScheme: colorScheme,
label: '30 ngày',
onTap: () {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final monthAgo = today.subtract(const Duration(days: 30));
ref.read(pointsHistoryFilterProvider.notifier).setDateRange(monthAgo, today);
},
),
const SizedBox(width: 8),
_buildQuickFilterChip(
context: context,
ref: ref,
colorScheme: colorScheme,
label: 'Năm nay',
onTap: () {
final now = DateTime.now();
ref.read(pointsHistoryFilterProvider.notifier).setDateRange(
DateTime(now.year, 1, 1),
DateTime(now.year, 12, 31),
);
},
),
const SizedBox(width: 8),
_buildQuickFilterChip(
context: context,
ref: ref,
colorScheme: colorScheme,
label: 'Tất cả',
onTap: () {
ref.read(pointsHistoryFilterProvider.notifier).clearFilter();
},
),
],
),
),
],
),
),
);
}
Widget _buildDateField({
required BuildContext context,
required ColorScheme colorScheme,
required String label,
required DateTime? value,
required DateFormat dateFormatter,
required VoidCallback onTap,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 4),
InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
value != null ? dateFormatter.format(value) : 'Chọn',
style: TextStyle(
fontSize: 14,
color: value != null ? colorScheme.onSurface : colorScheme.onSurfaceVariant,
),
),
FaIcon(
FontAwesomeIcons.calendar,
size: 14,
color: colorScheme.onSurfaceVariant,
),
],
),
),
),
],
);
}
Widget _buildQuickFilterChip({
required BuildContext context,
required WidgetRef ref,
required ColorScheme colorScheme,
required String label,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: colorScheme.outlineVariant),
),
child: Text(
label,
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurface,
),
),
),
);
}
/// Build transaction card with new design
Widget _buildTransactionCard(
BuildContext context,
@@ -258,30 +464,32 @@ class PointsHistoryPage extends ConsumerWidget {
);
}
/// Build empty state
Widget _buildEmptyState(ColorScheme colorScheme) {
return Center(
/// Build inline empty state (inside scrollable list)
Widget _buildEmptyStateInline(ColorScheme colorScheme) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 60),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FaIcon(
FontAwesomeIcons.clockRotateLeft,
size: 80,
size: 64,
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
),
const SizedBox(height: 16),
Text(
'Chưa có lịch sử điểm',
'Không có giao dịch',
style: TextStyle(
fontSize: 18,
fontSize: 16,
fontWeight: FontWeight.w600,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Text(
'Kéo xuống để làm mới',
'Không tìm thấy giao dịch trong khoảng thời gian này',
style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center,
),
],
),