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