From a5eb95fa647af9ca4cdc08a381165d0af142e2cc Mon Sep 17 00:00:00 2001 From: Phuoc Nguyen Date: Tue, 18 Nov 2025 11:23:07 +0700 Subject: [PATCH] add favorite --- docs/favorite.sh | 81 +++ docs/favorites_api_integration.md | 266 ++++++++ docs/favorites_loading_fix.md | 198 ++++++ html/address-create.html | 521 ++++++++++++++++ html/addresses.html | 113 +++- html/checkout.html | 586 ++++++++++-------- lib/core/constants/api_constants.dart | 19 + lib/core/constants/storage_constants.dart | 6 +- lib/core/database/hive_initializer.dart | 31 + lib/core/database/hive_service.dart | 4 +- .../favorite_products_local_datasource.dart | 69 +++ .../favorites_local_datasource.dart | 161 ----- .../favorites_remote_datasource.dart | 193 ++++++ .../favorites/data/models/favorite_model.dart | 120 ---- .../data/models/favorite_model.g.dart | 50 -- .../favorites_repository_impl.dart | 162 +++++ .../favorites/domain/entities/favorite.dart | 67 -- .../repositories/favorites_repository.dart | 29 + .../presentation/pages/favorites_page.dart | 170 ++++- .../providers/favorites_provider.dart | 250 ++++---- .../providers/favorites_provider.g.dart | 324 +++++----- .../widgets/favorite_product_card.dart | 6 +- .../products/data/models/product_model.dart | 53 ++ .../pages/product_detail_page.dart | 2 +- lib/hive_registrar.g.dart | 3 - 25 files changed, 2506 insertions(+), 978 deletions(-) create mode 100755 docs/favorite.sh create mode 100644 docs/favorites_api_integration.md create mode 100644 docs/favorites_loading_fix.md create mode 100644 html/address-create.html create mode 100644 lib/features/favorites/data/datasources/favorite_products_local_datasource.dart delete mode 100644 lib/features/favorites/data/datasources/favorites_local_datasource.dart create mode 100644 lib/features/favorites/data/datasources/favorites_remote_datasource.dart delete mode 100644 lib/features/favorites/data/models/favorite_model.dart delete mode 100644 lib/features/favorites/data/models/favorite_model.g.dart create mode 100644 lib/features/favorites/data/repositories/favorites_repository_impl.dart delete mode 100644 lib/features/favorites/domain/entities/favorite.dart create mode 100644 lib/features/favorites/domain/repositories/favorites_repository.dart diff --git a/docs/favorite.sh b/docs/favorite.sh new file mode 100755 index 0000000..fc5c597 --- /dev/null +++ b/docs/favorite.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# Favorites Feature Simplification Summary +# Date: 2025-11-18 +# +# CHANGES MADE: +# ============= +# +# 1. Simplified favorites_provider.dart +# - Removed old Favorites provider (Set of product IDs) +# - Kept only FavoriteProducts provider (List) +# - Helper providers now derive from FavoriteProducts: +# * isFavorite(productId) - checks if product is in list +# * favoriteCount() - counts products in list +# * favoriteProductIds() - maps to list of product IDs +# - Add/remove methods now use favoriteProductsProvider.notifier +# - No userId filtering - uses authenticated API session +# +# 2. Updated favorites_page.dart +# - Changed clearAll to show "under development" message +# - Still watches favoriteProductsProvider (already correct) +# +# 3. Updated favorite_product_card.dart +# - Changed from favoritesProvider.notifier to favoriteProductsProvider.notifier +# - Remove favorite now calls the correct provider +# +# 4. Updated product_detail_page.dart +# - Changed toggleFavorite from favoritesProvider.notifier to favoriteProductsProvider.notifier +# +# KEY BEHAVIORS: +# ============== +# +# - All favorites operations work with Product entities directly +# - No userId parameter needed in UI code +# - Repository methods still use 'current_user' as dummy userId for backwards compatibility +# - API calls use authenticated session (token-based) +# - Add/remove operations refresh the products list after success +# - Helper providers safely return defaults during loading/error states +# +# FILES MODIFIED: +# ============== +# 1. lib/features/favorites/presentation/providers/favorites_provider.dart +# 2. lib/features/favorites/presentation/pages/favorites_page.dart +# 3. lib/features/favorites/presentation/widgets/favorite_product_card.dart +# 4. lib/features/products/presentation/pages/product_detail_page.dart +# +# ARCHITECTURE: +# ============ +# +# Main Provider: +# FavoriteProducts (AsyncNotifierProvider>) +# ├── build() - loads products from repository +# ├── addFavorite(productId) - calls API, refreshes list +# ├── removeFavorite(productId) - calls API, refreshes list +# ├── toggleFavorite(productId) - adds or removes based on current state +# └── refresh() - manual refresh for pull-to-refresh +# +# Helper Providers (derived from FavoriteProducts): +# isFavorite(productId) - bool +# favoriteCount() - int +# favoriteProductIds() - List +# +# Data Flow: +# UI -> FavoriteProducts.notifier.method() -> Repository -> API +# API Response -> Repository caches locally -> Provider updates state +# Helper Providers watch FavoriteProducts and derive values +# +# TESTING: +# ======== +# To verify the changes work: +# 1. Add products to favorites from product detail page +# 2. View favorites page - should load product list +# 3. Remove products from favorites page +# 4. Toggle favorites from product cards +# 5. Check that favoriteCount updates in real-time +# 6. Test offline mode - should use cached products + +echo "Favorites feature simplified successfully!" +echo "Main provider: FavoriteProducts (List)" +echo "Helper providers derive from product list" +echo "No userId filtering - uses API auth session" diff --git a/docs/favorites_api_integration.md b/docs/favorites_api_integration.md new file mode 100644 index 0000000..ea49dbf --- /dev/null +++ b/docs/favorites_api_integration.md @@ -0,0 +1,266 @@ +# Favorites API Integration - Implementation Summary + +## Overview +Successfully integrated the Frappe ERPNext favorites/wishlist API with the Worker app using an **online-first approach**. The implementation follows clean architecture principles with proper separation of concerns. + +## API Endpoints (from docs/favorite.sh) + +### 1. Get Favorites List +``` +POST /api/method/building_material.building_material.api.item_wishlist.get_list +Body: { "limit_start": 0, "limit_page_length": 0 } +``` + +### 2. Add to Favorites +``` +POST /api/method/building_material.building_material.api.item_wishlist.add_to_wishlist +Body: { "item_id": "GIB20 G04" } +``` + +### 3. Remove from Favorites +``` +POST /api/method/building_material.building_material.api.item_wishlist.remove_from_wishlist +Body: { "item_id": "GIB20 G04" } +``` + +## Implementation Architecture + +### Files Created/Modified + +#### 1. API Constants +**File**: `lib/core/constants/api_constants.dart` +- Added favorites endpoints: + - `getFavorites` + - `addToFavorites` + - `removeFromFavorites` + +#### 2. Remote DataSource +**File**: `lib/features/favorites/data/datasources/favorites_remote_datasource.dart` +- `getFavorites()` - Fetch all favorites from API +- `addToFavorites(itemId)` - Add item to wishlist +- `removeFromFavorites(itemId)` - Remove item from wishlist +- Proper error handling with custom exceptions +- Maps API response to `FavoriteModel` + +#### 3. Domain Repository Interface +**File**: `lib/features/favorites/domain/repositories/favorites_repository.dart` +- Defines contract for favorites operations +- Documents online-first approach +- Methods: `getFavorites`, `addFavorite`, `removeFavorite`, `isFavorite`, `getFavoriteCount`, `clearFavorites`, `syncFavorites` + +#### 4. Repository Implementation +**File**: `lib/features/favorites/data/repositories/favorites_repository_impl.dart` +- **Online-first strategy**: + 1. Try API call when connected + 2. Update local cache with API response + 3. Fall back to local cache on network errors + 4. Queue changes for sync when offline + +**Key Methods**: +- `getFavorites()` - Fetches from API, caches locally, falls back to cache +- `addFavorite()` - Adds via API, caches locally, queues offline changes +- `removeFavorite()` - Removes via API, updates cache, queues offline changes +- `syncFavorites()` - Syncs pending changes when connection restored + +#### 5. Provider Updates +**File**: `lib/features/favorites/presentation/providers/favorites_provider.dart` + +**New Providers**: +- `favoritesRemoteDataSourceProvider` - Remote API datasource +- `favoritesRepositoryProvider` - Repository with online-first approach + +**Updated Favorites Provider**: +- Now uses repository instead of direct local datasource +- Supports online-first operations +- Auto-syncs with API on refresh +- Maintains backward compatibility with existing UI + +## Online-First Flow + +### Adding a Favorite +``` +User taps favorite icon + ↓ +Check network connectivity + ↓ +If ONLINE: + → Call API to add favorite + → Cache result locally + → Update UI state + ↓ +If OFFLINE: + → Add to local cache immediately + → Queue for sync (TODO) + → Update UI state + → Sync when connection restored +``` + +### Loading Favorites +``` +App loads favorites page + ↓ +Check network connectivity + ↓ +If ONLINE: + → Fetch from API + → Update local cache + → Display results + ↓ +If API FAILS: + → Fall back to local cache + → Display cached data + ↓ +If OFFLINE: + → Load from local cache + → Display cached data +``` + +### Removing a Favorite +``` +User removes favorite + ↓ +Check network connectivity + ↓ +If ONLINE: + → Call API to remove + → Update local cache + → Update UI state + ↓ +If OFFLINE: + → Remove from cache immediately + → Queue for sync (TODO) + → Update UI state + → Sync when connection restored +``` + +## Error Handling + +### Network Errors +- `NetworkException` - Connection issues, timeouts +- Falls back to local cache +- Shows cached data to user + +### Server Errors +- `ServerException` - 500 errors, invalid responses +- Falls back to local cache +- Logs error for debugging + +### Authentication Errors +- `UnauthorizedException` - 401/403 errors +- Prompts user to re-login +- Does not fall back to cache + +## Offline Queue (Future Enhancement) + +### TODO: Implement Sync Queue +Currently, offline changes are persisted locally but not automatically synced when connection is restored. + +**Future Implementation**: +1. Create offline queue datasource +2. Queue failed API calls with payload +3. Process queue on connection restore +4. Handle conflicts (item deleted on server, etc.) +5. Show sync status to user + +**Files to Create**: +- `lib/core/sync/offline_queue_datasource.dart` +- `lib/core/sync/sync_manager.dart` + +## Testing + +### Unit Tests (TODO) +- `test/features/favorites/data/datasources/favorites_remote_datasource_test.dart` +- `test/features/favorites/data/repositories/favorites_repository_impl_test.dart` +- `test/features/favorites/presentation/providers/favorites_provider_test.dart` + +### Integration Tests (TODO) +- Test online-first flow +- Test offline fallback +- Test sync after reconnection + +## Usage Example + +### In UI Code +```dart +// Add favorite +ref.read(favoritesProvider.notifier).addFavorite(productId); + +// Remove favorite +ref.read(favoritesProvider.notifier).removeFavorite(productId); + +// Check if favorited +final isFav = ref.watch(isFavoriteProvider(productId)); + +// Refresh from API +ref.read(favoritesProvider.notifier).refresh(); +``` + +## Benefits of This Implementation + +1. **Online-First** - Always uses fresh data when available +2. **Offline Support** - Works without network, syncs later +3. **Fast UI** - Immediate feedback from local cache +4. **Error Resilient** - Graceful fallback on failures +5. **Clean Architecture** - Easy to test and maintain +6. **Type Safe** - Full Dart/Flutter type checking + +## API Response Format + +### Get Favorites Response +```json +{ + "message": [ + { + "name": "GIB20 G04", + "item_code": "GIB20 G04", + "item_name": "Gibellina GIB20 G04", + "item_group_name": "OUTDOOR [20mm]", + "custom_link_360": "https://...", + "thumbnail": "https://...", + "price": 0, + "currency": "", + "conversion_of_sm": 5.5556 + } + ] +} +``` + +### Add/Remove Response +Standard Frappe response with status code 200 on success. + +## Configuration Required + +### Authentication +The API requires: +- `Cookie` header with `sid` (session ID) +- `X-Frappe-Csrf-Token` header + +These are automatically added by the `AuthInterceptor` in `lib/core/network/api_interceptor.dart`. + +### Base URL +Set in `lib/core/constants/api_constants.dart`: +```dart +static const String baseUrl = 'https://land.dbiz.com'; +``` + +## Next Steps + +1. **Test with real API** - Verify endpoints with actual backend +2. **Implement sync queue** - Handle offline changes properly +3. **Add error UI feedback** - Show sync status, errors to user +4. **Write unit tests** - Test all datasources and repository +5. **Add analytics** - Track favorite actions for insights +6. **Optimize caching** - Fine-tune cache expiration strategy + +## Notes + +- Current implementation uses hardcoded `userId = 'user_001'` (line 32 in favorites_provider.dart) +- TODO: Integrate with actual auth provider when available +- Offline queue sync is not yet implemented - changes are cached locally but not automatically synced +- All API calls use POST method as per Frappe ERPNext convention + +--- + +**Implementation Date**: December 2024 +**Status**: ✅ Complete - Ready for Testing +**Breaking Changes**: None - Backward compatible with existing UI diff --git a/docs/favorites_loading_fix.md b/docs/favorites_loading_fix.md new file mode 100644 index 0000000..19b9288 --- /dev/null +++ b/docs/favorites_loading_fix.md @@ -0,0 +1,198 @@ +# Favorites Page - Loading State Fix + +## Problem +Users were seeing the empty state ("Chưa có sản phẩm yêu thích") flash briefly before the actual favorites data loaded, even when they had favorites. This created a poor user experience. + +## Root Cause +The `favoriteProductsProvider` is an async provider that: +1. Loads favorites from API/cache +2. Fetches all products +3. Filters to get favorite products + +During this process, the provider goes through these states: +- **Loading** → Returns empty list [] → Shows loading skeleton +- **Data** → If products.isEmpty → Shows empty state ❌ **FLASH** +- **Data** → Returns actual products → Shows grid + +The flash happened because when the provider rebuilt, it momentarily returned an empty list before the actual data arrived. + +## Solution + +### 1. Added `ref.keepAlive()` to Providers +```dart +@riverpod +class Favorites extends _$Favorites { + @override + Future> build() async { + ref.keepAlive(); // ← Prevents provider disposal + // ... rest of code + } +} + +@riverpod +Future> favoriteProducts(Ref ref) async { + ref.keepAlive(); // ← Keeps previous data in memory + // ... rest of code +} +``` + +**Benefits**: +- Prevents state from being disposed when widget rebuilds +- Keeps previous data available via `favoriteProductsAsync.valueOrNull` +- Reduces unnecessary API calls + +### 2. Smart Loading State Logic + +```dart +loading: () { + // 1. Check for previous data first + final previousValue = favoriteProductsAsync.valueOrNull; + + // 2. If we have previous data, show it while loading + if (previousValue != null && previousValue.isNotEmpty) { + return Stack([ + _FavoritesGrid(products: previousValue), // Show old data + LoadingIndicator(), // Small loading badge on top + ]); + } + + // 3. Use favoriteCount as a hint + if (favoriteCount > 0) { + return LoadingState(); // Show skeleton + } + + // 4. No data, show skeleton (not empty state) + return LoadingState(); +} +``` + +### 3. Data State - Only Show Empty When Actually Empty + +```dart +data: (products) { + // Only show empty state when data is actually empty + if (products.isEmpty) { + return const _EmptyState(); + } + + return RefreshIndicator( + child: _FavoritesGrid(products: products), + ); +} +``` + +## User Experience Flow + +### Before Fix ❌ +``` +User opens favorites page + ↓ +Loading skeleton shows (0.1s) + ↓ +Empty state flashes (0.2s) ⚡ BAD UX + ↓ +Favorites grid appears +``` + +### After Fix ✅ +``` +User opens favorites page + ↓ +Loading skeleton shows (0.3s) + ↓ +Favorites grid appears smoothly +``` + +**OR** if returning to page: +``` +User opens favorites page (2nd time) + ↓ +Previous favorites show immediately + ↓ +Small "Đang tải..." badge appears at top + ↓ +Updated favorites appear (if changed) +``` + +## Additional Benefits + +### 1. Better Offline Support +- When offline, previous data stays visible +- Shows error banner on top instead of hiding content +- User can still browse cached favorites + +### 2. Faster Perceived Performance +- Instant display of previous data +- Users don't see empty states during reloads +- Smoother transitions + +### 3. Error Handling +```dart +error: (error, stackTrace) { + final previousValue = favoriteProductsAsync.valueOrNull; + + // Show previous data with error message + if (previousValue != null && previousValue.isNotEmpty) { + return Stack([ + _FavoritesGrid(products: previousValue), + ErrorBanner(onRetry: ...), + ]); + } + + // No previous data, show full error state + return _ErrorState(); +} +``` + +## Files Modified + +1. **lib/features/favorites/presentation/providers/favorites_provider.dart** + - Added `ref.keepAlive()` to `Favorites` class (line 81) + - Added `ref.keepAlive()` to `favoriteProducts` provider (line 271) + +2. **lib/features/favorites/presentation/pages/favorites_page.dart** + - Enhanced loading state logic (lines 138-193) + - Added previous value checking + - Added favoriteCount hint logic + +## Testing Checklist + +- [x] No empty state flash on first load +- [x] Smooth loading with skeleton +- [x] Previous data shown on subsequent visits +- [x] Loading indicator overlay when refreshing +- [ ] Test with slow network (3G) +- [ ] Test with offline mode +- [ ] Test with errors during load + +## Performance Impact + +✅ **Positive**: +- Reduced state rebuilds +- Better memory management with keepAlive +- Fewer API calls on navigation + +⚠️ **Watch**: +- Memory usage (keepAlive keeps data in memory) +- Can manually dispose with `ref.invalidate()` if needed + +## Future Improvements + +1. **Add shimmer duration control** + - Minimum shimmer display time to prevent flash + - Smooth fade transition from skeleton to content + +2. **Progressive loading** + - Show cached data first + - Overlay with "Updating..." badge + - Fade in updated items + +3. **Prefetch on app launch** + - Load favorites in background + - Data ready before user navigates to page + +--- + +**Status**: ✅ Implemented +**Impact**: High - Significantly improves perceived performance +**Breaking Changes**: None diff --git a/html/address-create.html b/html/address-create.html new file mode 100644 index 0000000..34c9048 --- /dev/null +++ b/html/address-create.html @@ -0,0 +1,521 @@ + + + + + + Thêm địa chỉ mới - EuroTile Worker + + + + + +
+ +
+ + + +

Thêm địa chỉ mới

+ +
+ +
+
+ + +
+

+ + Thông tin liên hệ +

+ +
+ +
+ + +
+
+ +
+ +
+ + +
+

Định dạng: 10-11 số

+
+ +
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+

+ + Địa chỉ giao hàng +

+ +
+ +
+ + +
+
+ +
+ +
+ + +
+
+ + + +
+ + +

Ví dụ: 123 Nguyễn Huệ, Khu phố 5

+
+
+ + +
+ +

+ Địa chỉ này sẽ được sử dụng làm mặc định khi đặt hàng +

+
+ + +
+
+ +
+ Lưu ý: Vui lòng kiểm tra kỹ thông tin địa chỉ để đảm bảo giao hàng chính xác. + Bạn có thể chỉnh sửa hoặc xóa địa chỉ này sau khi lưu. +
+
+
+
+
+ + +
+
+ +
+
+
+ + + + + + diff --git a/html/addresses.html b/html/addresses.html index 11aaddb..7873aca 100644 --- a/html/addresses.html +++ b/html/addresses.html @@ -3,11 +3,69 @@ - Địa chỉ đã lưu - EuroTile Worker + Địa chỉ của bạn - EuroTile Worker + +
@@ -15,12 +73,38 @@ -

Địa chỉ đã lưu

-
+ + + +
@@ -93,7 +177,7 @@
- @@ -133,6 +217,25 @@ alert('Đã đặt làm địa chỉ mặc định'); } + + function openInfoModal() { + document.getElementById('infoModal').style.display = 'flex'; + } + + function closeInfoModal() { + document.getElementById('infoModal').style.display = 'none'; + } + + function viewOrderDetail(orderId) { + window.location.href = `order-detail.html?id=${orderId}`; + } + + // Close modal when clicking outside + document.addEventListener('click', function(e) { + if (e.target.classList.contains('modal-overlay')) { + e.target.style.display = 'none'; + } + }); \ No newline at end of file diff --git a/html/checkout.html b/html/checkout.html index 0831f23..a1f34cd 100644 --- a/html/checkout.html +++ b/html/checkout.html @@ -4,12 +4,13 @@ Đặt hàng - EuroTile Worker - + - +
+ -
- -
-

Thông tin giao hàng

-
- - -
-
- - +
+ + +
+

+ + Thông tin giao hàng +

+ + +
- -
- - -
-
- - -
-
- - -
-
- - + +
+ +
+ + +
-
- - + +
+ +
- - -
-
-