diff --git a/docs/address.sh b/docs/address.sh index 7a29b59..1c5f9e4 100644 --- a/docs/address.sh +++ b/docs/address.sh @@ -25,4 +25,13 @@ curl --location 'https://land.dbiz.com//api/method/building_material.building_ma "city_code": "96", "ward_code": "32248", "is_default": false +}' + +#delete address +curl --location 'https://land.dbiz.com//api/method/building_material.building_material.api.address.delete' \ +--header 'Cookie: sid=a0c9a51c8d1fbbec824283115094bdca939bb829345e0005334aa99f; full_name=phuoc; sid=a0c9a51c8d1fbbec824283115094bdca939bb829345e0005334aa99f; system_user=no; user_id=vodanh.2901%40gmail.com; user_image=https%3A//secure.gravatar.com/avatar/753a0e2601b9bd87aed417e2ad123bf8%3Fd%3D404%26s%3D200' \ +--header 'X-Frappe-Csrf-Token: a22fa53eeaa923f71f2fd879d2863a0985a6f2107f5f7f66d34cd62d' \ +--header 'Content-Type: application/json' \ +--data '{ + "name": "Công ty Tiến Nguyễn-Billing" }' \ No newline at end of file diff --git a/html/profile-edit.html b/html/profile-edit.html index 7f45628..a4868ad 100644 --- a/html/profile-edit.html +++ b/html/profile-edit.html @@ -4,155 +4,394 @@ Thông tin cá nhân - EuroTile Worker - - - + + + - + +
-
- - +
+ + -

Thông tin cá nhân

-
+

Thông tin cá nhân

-
-
-
- -
-
- Avatar - +
+
+ + +
+
+ Avatar + + +
+

Nguyễn Văn A

+

Thầu thợ

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

Thông tin cá nhân

- - -
- - +
+
+ +
- - -
- - +
+ +
- - -
- - +
+ +
- - -
- - + +
+ +
- - -
- -
- - -
- - +
+ + +
+
+ + +
+
- -
- - + +
+
+ + Để thay đổi số điện thoại, email hoặc vai trò, vui lòng liên hệ bộ phận hỗ trợ.
- - - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- -
+
+ -
- -
-
+
+
\ No newline at end of file diff --git a/lib/features/account/presentation/pages/addresses_page.dart b/lib/features/account/presentation/pages/addresses_page.dart index fd10088..f4b6242 100644 --- a/lib/features/account/presentation/pages/addresses_page.dart +++ b/lib/features/account/presentation/pages/addresses_page.dart @@ -448,20 +448,20 @@ class AddressesPage extends HookConsumerWidget { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar( + const SnackBar( content: Row( children: [ - const FaIcon( + FaIcon( FontAwesomeIcons.circleCheck, color: Colors.white, size: 18, ), - const SizedBox(width: 12), - const Text('Đã xóa địa chỉ'), + SizedBox(width: 12), + Text('Đã xóa địa chỉ'), ], ), - backgroundColor: const Color(0xFF10B981), - duration: const Duration(seconds: 2), + backgroundColor: Color(0xFF10B981), + duration: Duration(seconds: 2), ), ); } diff --git a/lib/features/favorites/presentation/pages/favorites_page.dart b/lib/features/favorites/presentation/pages/favorites_page.dart index 206c5cd..485e4c1 100644 --- a/lib/features/favorites/presentation/pages/favorites_page.dart +++ b/lib/features/favorites/presentation/pages/favorites_page.dart @@ -5,9 +5,10 @@ library; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shimmer/shimmer.dart'; import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/theme/colors.dart'; @@ -19,12 +20,13 @@ import 'package:worker/features/products/domain/entities/product.dart'; /// /// Shows all products that the user has marked as favorites. /// Features: +/// - Search bar for filtering favorites /// - Grid layout of favorite products /// - Pull-to-refresh /// - Empty state when no favorites /// - Error state with retry /// - Clear all functionality -class FavoritesPage extends ConsumerWidget { +class FavoritesPage extends HookConsumerWidget { const FavoritesPage({super.key}); /// Show confirmation dialog before clearing all favorites @@ -76,18 +78,40 @@ class FavoritesPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + // Search controller + final searchController = useTextEditingController(); + final searchQuery = useState(''); + // Watch favorites and products final favoriteProductsAsync = ref.watch(favoriteProductsProvider); final favoriteCount = ref.watch(favoriteCountProvider); // Track if we've loaded data at least once to prevent empty state flash - final hasLoadedOnce = favoriteProductsAsync.hasValue || favoriteProductsAsync.hasError; + final hasLoadedOnce = + favoriteProductsAsync.hasValue || favoriteProductsAsync.hasError; + + // Filter products based on search query + List filterProducts(List products) { + if (searchQuery.value.isEmpty) return products; + + final query = searchQuery.value.toLowerCase().trim(); + return products.where((product) { + final matchesName = product.name.toLowerCase().contains(query); + final matchesSku = + product.erpnextItemCode?.toLowerCase().contains(query) ?? false; + return matchesName || matchesSku; + }).toList(); + } return Scaffold( backgroundColor: const Color(0xFFF4F6F8), appBar: AppBar( leading: IconButton( - icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), + icon: const FaIcon( + FontAwesomeIcons.arrowLeft, + color: Colors.black, + size: 20, + ), onPressed: () => context.pop(), ), title: const Text('Yêu thích', style: TextStyle(color: Colors.black)), @@ -115,7 +139,11 @@ class FavoritesPage extends ConsumerWidget { // Clear all button if (favoriteCount > 0) IconButton( - icon: const FaIcon(FontAwesomeIcons.trashCan, color: Colors.black, size: 20), + icon: const FaIcon( + FontAwesomeIcons.trashCan, + color: Colors.black, + size: 20, + ), tooltip: 'Xóa tất cả', onPressed: () => _showClearAllDialog(context, ref, favoriteCount), ), @@ -125,6 +153,9 @@ class FavoritesPage extends ConsumerWidget { body: SafeArea( child: favoriteProductsAsync.when( data: (products) { + // Filter products based on search + final filteredProducts = filterProducts(products); + // IMPORTANT: Only show empty state after we've confirmed data loaded // This prevents empty state flash during initial load if (products.isEmpty && hasLoadedOnce) { @@ -136,12 +167,119 @@ class FavoritesPage extends ConsumerWidget { return const _LoadingState(); } - return RefreshIndicator( - onRefresh: () async { - // Use the new refresh method from AsyncNotifier - await ref.read(favoriteProductsProvider.notifier).refresh(); - }, - child: _FavoritesGrid(products: products), + return Column( + children: [ + // Search Bar + Padding( + padding: const EdgeInsets.all(AppSpacing.md), + child: TextField( + controller: searchController, + onChanged: (value) => searchQuery.value = value, + decoration: InputDecoration( + hintText: 'Tìm kiếm sản phẩm...', + hintStyle: const TextStyle(color: AppColors.grey500), + prefixIcon: const Icon( + Icons.search, + color: AppColors.grey500, + ), + suffixIcon: searchQuery.value.isNotEmpty + ? IconButton( + icon: const Icon( + Icons.clear, + color: AppColors.grey500, + ), + onPressed: () { + searchController.clear(); + searchQuery.value = ''; + }, + ) + : null, + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppRadius.button), + borderSide: const BorderSide(color: AppColors.grey100), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppRadius.button), + borderSide: const BorderSide(color: AppColors.grey100), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppRadius.button), + borderSide: const BorderSide( + color: AppColors.primaryBlue, + width: 2, + ), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: AppSpacing.md, + vertical: AppSpacing.sm, + ), + ), + ), + ), + + // Results count when searching + if (searchQuery.value.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.md, + ), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + 'Tìm thấy ${filteredProducts.length} sản phẩm', + style: const TextStyle( + fontSize: 14, + color: AppColors.grey500, + ), + ), + ), + ), + + // Products Grid + Expanded( + child: + filteredProducts.isEmpty && searchQuery.value.isNotEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const FaIcon( + FontAwesomeIcons.magnifyingGlass, + size: 64, + color: AppColors.grey500, + ), + const SizedBox(height: AppSpacing.md), + Text( + 'Không tìm thấy "${searchQuery.value}"', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.grey900, + ), + ), + const SizedBox(height: AppSpacing.sm), + const Text( + 'Thử tìm kiếm với từ khóa khác', + style: TextStyle( + fontSize: 14, + color: AppColors.grey500, + ), + ), + ], + ), + ) + : RefreshIndicator( + onRefresh: () async { + await ref + .read(favoriteProductsProvider.notifier) + .refresh(); + }, + child: _FavoritesGrid(products: filteredProducts), + ), + ), + ], ); }, loading: () { @@ -156,7 +294,9 @@ class FavoritesPage extends ConsumerWidget { children: [ RefreshIndicator( onRefresh: () async { - await ref.read(favoriteProductsProvider.notifier).refresh(); + await ref + .read(favoriteProductsProvider.notifier) + .refresh(); }, child: _FavoritesGrid(products: previousValue), ), @@ -177,7 +317,9 @@ class FavoritesPage extends ConsumerWidget { SizedBox( width: 16, height: 16, - child: CircularProgressIndicator(strokeWidth: 2), + child: CircularProgressIndicator( + strokeWidth: 2, + ), ), SizedBox(width: 8), Text('Đang tải...'), @@ -212,7 +354,9 @@ class FavoritesPage extends ConsumerWidget { children: [ RefreshIndicator( onRefresh: () async { - await ref.read(favoriteProductsProvider.notifier).refresh(); + await ref + .read(favoriteProductsProvider.notifier) + .refresh(); }, child: _FavoritesGrid(products: previousValue), ), @@ -241,7 +385,9 @@ class FavoritesPage extends ConsumerWidget { ), TextButton( onPressed: () async { - await ref.read(favoriteProductsProvider.notifier).refresh(); + await ref + .read(favoriteProductsProvider.notifier) + .refresh(); }, child: const Text( 'Thử lại',