asdasdasd

This commit is contained in:
Phuoc Nguyen
2025-10-29 16:12:37 +07:00
parent cb4df363ab
commit c12869b01f
11 changed files with 394 additions and 51 deletions

View File

@@ -1,8 +1,10 @@
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/di/providers.dart';
import '../../../../core/services/print_service.dart';
import '../../../../core/utils/text_utils.dart';
import '../../../users/domain/entities/user_entity.dart';
import '../../data/models/create_product_warehouse_request.dart';
import '../../domain/entities/product_stage_entity.dart';
@@ -332,7 +334,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
@@ -821,45 +823,116 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButtonFormField<UserEntity>(
value: value,
decoration: InputDecoration(
labelText: label,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: theme.colorScheme.outline,
DropdownSearch<UserEntity>(
items: (filter, infiniteScrollProps) => users,
selectedItem: value,
itemAsString: (UserEntity user) {
return user.name.isNotEmpty
? '${user.name} ${user.firstName}'
: user.email;
},
compareFn: (item1, item2) => item1.id == item2.id,
// Custom filter function for Vietnamese-aware search
filterFn: (user, filter) {
if (filter.isEmpty) return true;
// Search in name, firstName, and email
final searchTexts = [
user.name,
user.firstName,
user.email,
'${user.name} ${user.firstName}', // Full name
];
// Use Vietnamese-aware search
return TextUtils.containsVietnameseSearchInAny(searchTexts, filter);
},
popupProps: PopupProps.menu(
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
labelText: 'Tìm kiếm',
hintText: 'Nhập tên hoặc email...',
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: theme.colorScheme.primary,
width: 2,
),
menuProps: const MenuProps(
borderRadius: BorderRadius.all(Radius.circular(8)),
elevation: 8,
),
filled: true,
fillColor: theme.colorScheme.surface,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
itemBuilder: (context, item, isDisabled, isSelected) {
return ListTile(
selected: isSelected,
dense: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
title: Text(
item.name.isNotEmpty
? '${item.name} ${item.firstName}'
: item.email,
overflow: TextOverflow.ellipsis,
),
subtitle: item.email.isNotEmpty && item.name.isNotEmpty
? Text(
item.email,
style: Theme.of(context).textTheme.bodySmall,
overflow: TextOverflow.ellipsis,
)
: null,
);
},
emptyBuilder: (context, searchEntry) {
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Không tìm thấy kết quả',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
),
);
},
),
decoratorProps: DropDownDecoratorProps(
decoration: InputDecoration(
labelText: label,
hintText: 'Chọn $label',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: theme.colorScheme.outline,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: theme.colorScheme.primary,
width: 2,
),
),
filled: true,
fillColor: theme.colorScheme.surface,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
),
hint: Text('Chọn $label'),
items: users.map((user) {
return DropdownMenuItem<UserEntity>(
value: user,
child: Text(
user.name.isNotEmpty ? '${user.name} ${user.firstName}' : user.email,
overflow: TextOverflow.ellipsis,
),
);
}).toList(),
onChanged: onChanged,
isExpanded: true,
),
],
);

View File

@@ -56,21 +56,23 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
_isTabSwitching = true; // Mark that tab is switching
});
// Load products for new operation type
// Load products for new operation type from cache (forceRefresh: false)
ref.read(productsProvider.notifier).loadProducts(
widget.warehouseId,
widget.warehouseName,
_currentOperationType,
forceRefresh: false, // Load from cache when switching tabs
);
}
});
// Load products when page is initialized
// Load products from cache when page is initialized (forceRefresh: false)
Future.microtask(() {
ref.read(productsProvider.notifier).loadProducts(
widget.warehouseId,
widget.warehouseName,
_currentOperationType,
forceRefresh: false, // Load from cache on initial load
);
});
}

View File

@@ -52,11 +52,13 @@ class ProductsNotifier extends StateNotifier<ProductsState> {
/// [warehouseId] - The ID of the warehouse
/// [warehouseName] - The name of the warehouse (for display)
/// [type] - The operation type ('import' or 'export')
/// [forceRefresh] - If true, bypass cache and fetch from API
Future<void> loadProducts(
int warehouseId,
String warehouseName,
String type,
) async {
String type, {
bool forceRefresh = false,
}) async {
// Set loading state
state = state.copyWith(
isLoading: true,
@@ -66,8 +68,12 @@ class ProductsNotifier extends StateNotifier<ProductsState> {
operationType: type,
);
// Call the use case
final result = await getProductsUseCase(warehouseId, type);
// Call the use case with forceRefresh flag
final result = await getProductsUseCase(
warehouseId,
type,
forceRefresh: forceRefresh,
);
// Handle the result
result.fold(
@@ -95,13 +101,14 @@ class ProductsNotifier extends StateNotifier<ProductsState> {
state = const ProductsState();
}
/// Refresh products
/// Refresh products - forces fetch from API
Future<void> refreshProducts() async {
if (state.warehouseId != null) {
await loadProducts(
state.warehouseId!,
state.warehouseName ?? '',
state.operationType,
forceRefresh: true, // Always force refresh when explicitly requested
);
}
}