fix product search/filter
This commit is contained in:
@@ -64,10 +64,7 @@ class ProductsRemoteDataSource {
|
||||
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
url,
|
||||
data: {
|
||||
'limit_start': limitStart,
|
||||
'limit_page_length': limitPageLength,
|
||||
},
|
||||
data: {'limit_start': limitStart, 'limit_page_length': limitPageLength},
|
||||
options: Options(headers: headers),
|
||||
);
|
||||
|
||||
@@ -83,7 +80,9 @@ class ProductsRemoteDataSource {
|
||||
|
||||
final productsList = message as List;
|
||||
return productsList
|
||||
.map((item) => ProductModel.fromFrappeJson(item as Map<String, dynamic>))
|
||||
.map(
|
||||
(item) => ProductModel.fromFrappeJson(item as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
} on DioException catch (e) {
|
||||
if (e.response?.statusCode == 404) {
|
||||
@@ -125,9 +124,7 @@ class ProductsRemoteDataSource {
|
||||
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
url,
|
||||
data: {
|
||||
'name': itemCode,
|
||||
},
|
||||
data: {'name': itemCode},
|
||||
options: Options(headers: headers),
|
||||
);
|
||||
|
||||
@@ -161,25 +158,72 @@ class ProductsRemoteDataSource {
|
||||
|
||||
/// Search products
|
||||
///
|
||||
/// Searches products by name or description.
|
||||
/// For now, we fetch all products and filter locally.
|
||||
/// In the future, the API might support server-side search.
|
||||
Future<List<ProductModel>> searchProducts(String query) async {
|
||||
// For now, fetch all products and filter locally
|
||||
// TODO: Implement server-side search if API supports it
|
||||
final allProducts = await getAllProducts();
|
||||
/// Searches products by keyword using Frappe API with pagination support.
|
||||
/// Uses the search_keyword parameter from the API.
|
||||
///
|
||||
/// API endpoint: POST https://land.dbiz.com/api/method/building_material.building_material.api.item.get_list
|
||||
/// Request body:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "limit_start": 0,
|
||||
/// "limit_page_length": 12,
|
||||
/// "search_keyword": "gạch men"
|
||||
/// }
|
||||
/// ```
|
||||
Future<List<ProductModel>> searchProducts(
|
||||
String query, {
|
||||
int limitStart = 0,
|
||||
int limitPageLength = 12,
|
||||
}) async {
|
||||
try {
|
||||
// Get Frappe session headers
|
||||
final headers = await _frappeAuthService.getHeaders();
|
||||
|
||||
final lowercaseQuery = query.toLowerCase();
|
||||
// Build full API URL
|
||||
const url =
|
||||
'${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeGetItems}';
|
||||
|
||||
return allProducts.where((product) {
|
||||
final name = product.name.toLowerCase();
|
||||
final description = (product.description ?? '').toLowerCase();
|
||||
final productId = product.productId.toLowerCase();
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
url,
|
||||
data: {
|
||||
'limit_start': limitStart,
|
||||
'limit_page_length': limitPageLength,
|
||||
'search_keyword': query,
|
||||
},
|
||||
options: Options(headers: headers),
|
||||
);
|
||||
|
||||
return name.contains(lowercaseQuery) ||
|
||||
description.contains(lowercaseQuery) ||
|
||||
productId.contains(lowercaseQuery);
|
||||
}).toList();
|
||||
if (response.data == null) {
|
||||
throw Exception('Empty response from server');
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
final message = response.data!['message'];
|
||||
if (message == null) {
|
||||
throw Exception('No message field in response');
|
||||
}
|
||||
|
||||
final productsList = message as List;
|
||||
return productsList
|
||||
.map(
|
||||
(item) => ProductModel.fromFrappeJson(item as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
} on DioException catch (e) {
|
||||
if (e.response?.statusCode == 404) {
|
||||
throw Exception('Search endpoint not found');
|
||||
} else if (e.response?.statusCode == 500) {
|
||||
throw Exception('Server error while searching products');
|
||||
} else if (e.type == DioExceptionType.connectionTimeout) {
|
||||
throw Exception('Connection timeout while searching products');
|
||||
} else if (e.type == DioExceptionType.receiveTimeout) {
|
||||
throw Exception('Response timeout while searching products');
|
||||
} else {
|
||||
throw Exception('Failed to search products: ${e.message}');
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Unexpected error searching products: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get products by category
|
||||
@@ -302,9 +346,7 @@ class ProductsRemoteDataSource {
|
||||
throw Exception('No message field in response');
|
||||
}
|
||||
|
||||
return (message as List)
|
||||
.map((item) => item['name'] as String)
|
||||
.toList();
|
||||
return (message as List).map((item) => item['name'] as String).toList();
|
||||
} on DioException catch (e) {
|
||||
if (e.response?.statusCode == 404) {
|
||||
throw Exception('Product brands endpoint not found');
|
||||
@@ -368,4 +410,108 @@ class ProductsRemoteDataSource {
|
||||
throw Exception('Unexpected error fetching product attributes: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get products with filters (Complete API - Final Version)
|
||||
///
|
||||
/// Fetches products with support for all filter combinations:
|
||||
/// - item_group: List of product groups/categories
|
||||
/// - brand: List of brands
|
||||
/// - item_attribute: List of attribute filters (color, size, surface, etc.)
|
||||
/// - search_keyword: Search query
|
||||
/// - Pagination support
|
||||
///
|
||||
/// API endpoint: POST https://land.dbiz.com/api/method/building_material.building_material.api.item.get_list
|
||||
/// Request body:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "limit_start": 0,
|
||||
/// "limit_page_length": 12,
|
||||
/// "item_group": ["CẨM THẠCH [ Marble ]"],
|
||||
/// "brand": ["TEST 1"],
|
||||
/// "item_attribute": [
|
||||
/// {
|
||||
/// "attribute": "Màu sắc",
|
||||
/// "attribute_value": "Nhạt"
|
||||
/// }
|
||||
/// ],
|
||||
/// "search_keyword": "gạch"
|
||||
/// }
|
||||
/// ```
|
||||
Future<List<ProductModel>> getProductsWithFilters({
|
||||
int limitStart = 0,
|
||||
int limitPageLength = 12,
|
||||
List<String>? itemGroups,
|
||||
List<String>? brands,
|
||||
List<Map<String, String>>? itemAttributes,
|
||||
String? searchKeyword,
|
||||
}) async {
|
||||
try {
|
||||
// Get Frappe session headers
|
||||
final headers = await _frappeAuthService.getHeaders();
|
||||
|
||||
// Build full API URL
|
||||
const url =
|
||||
'${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeGetItems}';
|
||||
|
||||
// Build request data
|
||||
final Map<String, dynamic> requestData = {
|
||||
'limit_start': limitStart,
|
||||
'limit_page_length': limitPageLength,
|
||||
};
|
||||
|
||||
// Add filters only if they have values
|
||||
if (itemGroups != null && itemGroups.isNotEmpty) {
|
||||
requestData['item_group'] = itemGroups;
|
||||
}
|
||||
|
||||
if (brands != null && brands.isNotEmpty) {
|
||||
requestData['brand'] = brands;
|
||||
}
|
||||
|
||||
if (itemAttributes != null && itemAttributes.isNotEmpty) {
|
||||
requestData['item_attribute'] = itemAttributes;
|
||||
}
|
||||
|
||||
if (searchKeyword != null && searchKeyword.isNotEmpty) {
|
||||
requestData['search_keyword'] = searchKeyword;
|
||||
}
|
||||
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
url,
|
||||
data: requestData,
|
||||
options: Options(headers: headers),
|
||||
);
|
||||
|
||||
if (response.data == null) {
|
||||
throw Exception('Empty response from server');
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
final message = response.data!['message'];
|
||||
if (message == null) {
|
||||
throw Exception('No message field in response');
|
||||
}
|
||||
|
||||
final productsList = message as List;
|
||||
return productsList
|
||||
.map(
|
||||
(item) => ProductModel.fromFrappeJson(item as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
} on DioException catch (e) {
|
||||
if (e.response?.statusCode == 404) {
|
||||
throw Exception('Products endpoint not found');
|
||||
} else if (e.response?.statusCode == 500) {
|
||||
throw Exception('Server error while fetching filtered products');
|
||||
} else if (e.type == DioExceptionType.connectionTimeout) {
|
||||
throw Exception('Connection timeout while fetching filtered products');
|
||||
} else if (e.type == DioExceptionType.receiveTimeout) {
|
||||
throw Exception('Response timeout while fetching filtered products');
|
||||
} else {
|
||||
throw Exception('Failed to fetch filtered products: ${e.message}');
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Unexpected error fetching filtered products: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,18 @@ class ProductsRepositoryImpl implements ProductsRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Product>> searchProducts(String query) async {
|
||||
Future<List<Product>> searchProducts(
|
||||
String query, {
|
||||
int limitStart = 0,
|
||||
int limitPageLength = 12,
|
||||
}) async {
|
||||
try {
|
||||
// Search via remote API
|
||||
final productModels = await remoteDataSource.searchProducts(query);
|
||||
// Search via remote API with pagination
|
||||
final productModels = await remoteDataSource.searchProducts(
|
||||
query,
|
||||
limitStart: limitStart,
|
||||
limitPageLength: limitPageLength,
|
||||
);
|
||||
return productModels.map((model) => model.toEntity()).toList();
|
||||
} catch (e) {
|
||||
print('[ProductsRepository] Error searching products: $e');
|
||||
@@ -98,4 +106,30 @@ class ProductsRepositoryImpl implements ProductsRepository {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Product>> getProductsWithFilters({
|
||||
int limitStart = 0,
|
||||
int limitPageLength = 12,
|
||||
List<String>? itemGroups,
|
||||
List<String>? brands,
|
||||
List<Map<String, String>>? itemAttributes,
|
||||
String? searchKeyword,
|
||||
}) async {
|
||||
try {
|
||||
// Fetch from Frappe API with all filters and pagination
|
||||
final productModels = await remoteDataSource.getProductsWithFilters(
|
||||
limitStart: limitStart,
|
||||
limitPageLength: limitPageLength,
|
||||
itemGroups: itemGroups,
|
||||
brands: brands,
|
||||
itemAttributes: itemAttributes,
|
||||
searchKeyword: searchKeyword,
|
||||
);
|
||||
return productModels.map((model) => model.toEntity()).toList();
|
||||
} catch (e) {
|
||||
print('[ProductsRepository] Error getting filtered products: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user