This commit is contained in:
Phuoc Nguyen
2025-10-24 17:35:39 +07:00
parent 82ce30961b
commit 860a8788b6
17 changed files with 2572 additions and 32 deletions

View File

@@ -0,0 +1,176 @@
/// Gifts Catalog Provider
///
/// State management for gift catalog and filtering.
library;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:worker/features/loyalty/domain/entities/gift_catalog.dart';
part 'gifts_provider.g.dart';
/// Gift catalog provider
///
/// Provides the complete list of available gifts.
/// Currently returns mock data matching the HTML design.
@riverpod
class Gifts extends _$Gifts {
@override
List<GiftCatalog> build() {
// Mock gift catalog matching HTML design
return _getMockGifts();
}
/// Refresh gift catalog
Future<void> refresh() async {
// TODO: Fetch from API
state = _getMockGifts();
}
/// Get mock gifts data
List<GiftCatalog> _getMockGifts() {
final now = DateTime.now();
return [
GiftCatalog(
catalogId: 'gift_001',
name: 'Voucher 500.000đ',
description: 'Áp dụng cho đơn từ 5 triệu',
imageUrl:
'https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=300&h=200&fit=crop',
category: GiftCategory.voucher,
pointsCost: 2500,
cashValue: 500000,
quantityAvailable: 100,
quantityRedeemed: 20,
isActive: true,
createdAt: now,
updatedAt: now,
),
GiftCatalog(
catalogId: 'gift_002',
name: 'Bộ keo chà ron cao cấp',
description: 'Weber.color comfort',
imageUrl:
'https://images.unsplash.com/photo-1607082348824-0a96f2a4b9da?w=300&h=200&fit=crop',
category: GiftCategory.product,
pointsCost: 3000,
quantityAvailable: 50,
quantityRedeemed: 15,
isActive: true,
createdAt: now,
updatedAt: now,
),
GiftCatalog(
catalogId: 'gift_003',
name: 'Tư vấn thiết kế miễn phí',
description: '1 buổi tư vấn tại nhà',
imageUrl:
'https://images.unsplash.com/photo-1540932239986-30128078f3c5?w=300&h=200&fit=crop',
category: GiftCategory.service,
pointsCost: 5000,
quantityAvailable: 30,
quantityRedeemed: 8,
isActive: true,
createdAt: now,
updatedAt: now,
),
GiftCatalog(
catalogId: 'gift_004',
name: 'Gạch trang trí Premium',
description: 'Bộ 10m² gạch cao cấp',
imageUrl:
'https://images.unsplash.com/photo-1615874694520-474822394e73?w=300&h=200&fit=crop',
category: GiftCategory.product,
pointsCost: 8000,
quantityAvailable: 25,
quantityRedeemed: 5,
isActive: true,
createdAt: now,
updatedAt: now,
),
GiftCatalog(
catalogId: 'gift_005',
name: 'Áo thun EuroTile',
description: 'Limited Edition 2023',
imageUrl:
'https://images.unsplash.com/photo-1523381210434-271e8be1f52b?w=300&h=200&fit=crop',
category: GiftCategory.product,
pointsCost: 1500,
quantityAvailable: 200,
quantityRedeemed: 50,
isActive: true,
createdAt: now,
updatedAt: now,
),
GiftCatalog(
catalogId: 'gift_006',
name: 'Nâng hạng thẻ Platinum',
description: 'Ưu đãi cao cấp 1 năm',
imageUrl:
'https://images.unsplash.com/photo-1556742400-b5b7a512f3d7?w=300&h=200&fit=crop',
category: GiftCategory.other,
pointsCost: 15000,
quantityAvailable: 10,
quantityRedeemed: 2,
isActive: true,
createdAt: now,
updatedAt: now,
),
];
}
}
/// Selected gift category state provider
@riverpod
class SelectedGiftCategory extends _$SelectedGiftCategory {
@override
GiftCategory? build() {
// null means "All" is selected
return null;
}
/// Set selected category
void setCategory(GiftCategory? category) {
state = category;
}
/// Clear selection (show all)
void clearSelection() {
state = null;
}
}
/// Filtered gifts provider
///
/// Filters gifts based on selected category.
@riverpod
List<GiftCatalog> filteredGifts(Ref ref) {
final allGifts = ref.watch(giftsProvider);
final selectedCategory = ref.watch(selectedGiftCategoryProvider);
// If no category selected, return all gifts
if (selectedCategory == null) {
return allGifts;
}
// Filter by selected category
return allGifts.where((gift) => gift.category == selectedCategory).toList();
}
/// Vietnamese category display names
extension GiftCategoryVi on GiftCategory {
String get displayNameVi {
switch (this) {
case GiftCategory.voucher:
return 'Voucher';
case GiftCategory.product:
return 'Sản phẩm';
case GiftCategory.service:
return 'Dịch vụ';
case GiftCategory.discount:
return 'Ưu đãi đặc biệt';
case GiftCategory.other:
return 'Khác';
}
}
}

View File

@@ -0,0 +1,197 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'gifts_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Gift catalog provider
///
/// Provides the complete list of available gifts.
/// Currently returns mock data matching the HTML design.
@ProviderFor(Gifts)
const giftsProvider = GiftsProvider._();
/// Gift catalog provider
///
/// Provides the complete list of available gifts.
/// Currently returns mock data matching the HTML design.
final class GiftsProvider extends $NotifierProvider<Gifts, List<GiftCatalog>> {
/// Gift catalog provider
///
/// Provides the complete list of available gifts.
/// Currently returns mock data matching the HTML design.
const GiftsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'giftsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$giftsHash();
@$internal
@override
Gifts create() => Gifts();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(List<GiftCatalog> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<List<GiftCatalog>>(value),
);
}
}
String _$giftsHash() => r'b931265843ea3f87f93513d579a4ccda8a327bdc';
/// Gift catalog provider
///
/// Provides the complete list of available gifts.
/// Currently returns mock data matching the HTML design.
abstract class _$Gifts extends $Notifier<List<GiftCatalog>> {
List<GiftCatalog> build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<List<GiftCatalog>, List<GiftCatalog>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<List<GiftCatalog>, List<GiftCatalog>>,
List<GiftCatalog>,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// Selected gift category state provider
@ProviderFor(SelectedGiftCategory)
const selectedGiftCategoryProvider = SelectedGiftCategoryProvider._();
/// Selected gift category state provider
final class SelectedGiftCategoryProvider
extends $NotifierProvider<SelectedGiftCategory, GiftCategory?> {
/// Selected gift category state provider
const SelectedGiftCategoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedGiftCategoryProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedGiftCategoryHash();
@$internal
@override
SelectedGiftCategory create() => SelectedGiftCategory();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(GiftCategory? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<GiftCategory?>(value),
);
}
}
String _$selectedGiftCategoryHash() =>
r'c34e985518a6c7fbd22376b78db02e39d1f55279';
/// Selected gift category state provider
abstract class _$SelectedGiftCategory extends $Notifier<GiftCategory?> {
GiftCategory? build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<GiftCategory?, GiftCategory?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<GiftCategory?, GiftCategory?>,
GiftCategory?,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// Filtered gifts provider
///
/// Filters gifts based on selected category.
@ProviderFor(filteredGifts)
const filteredGiftsProvider = FilteredGiftsProvider._();
/// Filtered gifts provider
///
/// Filters gifts based on selected category.
final class FilteredGiftsProvider
extends
$FunctionalProvider<
List<GiftCatalog>,
List<GiftCatalog>,
List<GiftCatalog>
>
with $Provider<List<GiftCatalog>> {
/// Filtered gifts provider
///
/// Filters gifts based on selected category.
const FilteredGiftsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'filteredGiftsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$filteredGiftsHash();
@$internal
@override
$ProviderElement<List<GiftCatalog>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
List<GiftCatalog> create(Ref ref) {
return filteredGifts(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(List<GiftCatalog> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<List<GiftCatalog>>(value),
);
}
}
String _$filteredGiftsHash() => r'361ef8e7727e5577adf408a9ca4c577af3490328';

View File

@@ -0,0 +1,121 @@
/// Loyalty Points Provider
///
/// State management for user's loyalty points and balance information.
library;
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'loyalty_points_provider.g.dart';
/// Loyalty Points State
///
/// Contains user's available points and expiration information.
class LoyaltyPointsState {
/// Current available points
final int availablePoints;
/// Points that will expire soon
final int expiringPoints;
/// Expiration date for expiring points
final DateTime? expirationDate;
/// Points earned this month
final int earnedThisMonth;
/// Points spent this month
final int spentThisMonth;
const LoyaltyPointsState({
required this.availablePoints,
required this.expiringPoints,
this.expirationDate,
required this.earnedThisMonth,
required this.spentThisMonth,
});
/// Factory for initial state
factory LoyaltyPointsState.initial() {
return const LoyaltyPointsState(
availablePoints: 0,
expiringPoints: 0,
expirationDate: null,
earnedThisMonth: 0,
spentThisMonth: 0,
);
}
/// Copy with method
LoyaltyPointsState copyWith({
int? availablePoints,
int? expiringPoints,
DateTime? expirationDate,
int? earnedThisMonth,
int? spentThisMonth,
}) {
return LoyaltyPointsState(
availablePoints: availablePoints ?? this.availablePoints,
expiringPoints: expiringPoints ?? this.expiringPoints,
expirationDate: expirationDate ?? this.expirationDate,
earnedThisMonth: earnedThisMonth ?? this.earnedThisMonth,
spentThisMonth: spentThisMonth ?? this.spentThisMonth,
);
}
}
/// Loyalty Points Provider
///
/// Manages user's loyalty points balance.
/// Currently returns mock data matching the HTML design.
@riverpod
class LoyaltyPoints extends _$LoyaltyPoints {
@override
LoyaltyPointsState build() {
// Mock data matching HTML design: 9,750 points available
// 1,200 points expiring on 31/12/2023
return LoyaltyPointsState(
availablePoints: 9750,
expiringPoints: 1200,
expirationDate: DateTime(2023, 12, 31),
earnedThisMonth: 2500,
spentThisMonth: 800,
);
}
/// Refresh points data
Future<void> refresh() async {
// TODO: Fetch from API
// For now, just reset to mock data
state = build();
}
/// Deduct points after redemption
void deductPoints(int points) {
if (state.availablePoints >= points) {
state = state.copyWith(
availablePoints: state.availablePoints - points,
spentThisMonth: state.spentThisMonth + points,
);
}
}
/// Add points (e.g., from earning activity)
void addPoints(int points) {
state = state.copyWith(
availablePoints: state.availablePoints + points,
earnedThisMonth: state.earnedThisMonth + points,
);
}
/// Check if user has enough points for a redemption
bool hasEnoughPoints(int requiredPoints) {
return state.availablePoints >= requiredPoints;
}
}
/// Provider to check if user has enough points for a specific amount
@riverpod
bool hasEnoughPoints(Ref ref, int requiredPoints) {
final points = ref.watch(loyaltyPointsProvider).availablePoints;
return points >= requiredPoints;
}

View File

@@ -0,0 +1,166 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'loyalty_points_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Loyalty Points Provider
///
/// Manages user's loyalty points balance.
/// Currently returns mock data matching the HTML design.
@ProviderFor(LoyaltyPoints)
const loyaltyPointsProvider = LoyaltyPointsProvider._();
/// Loyalty Points Provider
///
/// Manages user's loyalty points balance.
/// Currently returns mock data matching the HTML design.
final class LoyaltyPointsProvider
extends $NotifierProvider<LoyaltyPoints, LoyaltyPointsState> {
/// Loyalty Points Provider
///
/// Manages user's loyalty points balance.
/// Currently returns mock data matching the HTML design.
const LoyaltyPointsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'loyaltyPointsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$loyaltyPointsHash();
@$internal
@override
LoyaltyPoints create() => LoyaltyPoints();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(LoyaltyPointsState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<LoyaltyPointsState>(value),
);
}
}
String _$loyaltyPointsHash() => r'b1ee61ad335a1c23bf481567a49d15d7f8f0b018';
/// Loyalty Points Provider
///
/// Manages user's loyalty points balance.
/// Currently returns mock data matching the HTML design.
abstract class _$LoyaltyPoints extends $Notifier<LoyaltyPointsState> {
LoyaltyPointsState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<LoyaltyPointsState, LoyaltyPointsState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<LoyaltyPointsState, LoyaltyPointsState>,
LoyaltyPointsState,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// Provider to check if user has enough points for a specific amount
@ProviderFor(hasEnoughPoints)
const hasEnoughPointsProvider = HasEnoughPointsFamily._();
/// Provider to check if user has enough points for a specific amount
final class HasEnoughPointsProvider
extends $FunctionalProvider<bool, bool, bool>
with $Provider<bool> {
/// Provider to check if user has enough points for a specific amount
const HasEnoughPointsProvider._({
required HasEnoughPointsFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'hasEnoughPointsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$hasEnoughPointsHash();
@override
String toString() {
return r'hasEnoughPointsProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<bool> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
bool create(Ref ref) {
final argument = this.argument as int;
return hasEnoughPoints(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(bool value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<bool>(value),
);
}
@override
bool operator ==(Object other) {
return other is HasEnoughPointsProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$hasEnoughPointsHash() => r'9034d31521d9eb906fb4cd10dc7fc6bc5e6249bf';
/// Provider to check if user has enough points for a specific amount
final class HasEnoughPointsFamily extends $Family
with $FunctionalFamilyOverride<bool, int> {
const HasEnoughPointsFamily._()
: super(
retry: null,
name: r'hasEnoughPointsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
/// Provider to check if user has enough points for a specific amount
HasEnoughPointsProvider call(int requiredPoints) =>
HasEnoughPointsProvider._(argument: requiredPoints, from: this);
@override
String toString() => r'hasEnoughPointsProvider';
}