From 911f4e82d993915dab8eb52f55972c76fd658c84 Mon Sep 17 00:00:00 2001 From: Yasien Mac Mini Date: Wed, 30 Jul 2025 14:02:52 +0200 Subject: [PATCH] enhanced search by gemini --- .../build_favourite_businesses_list.dart | 112 ++++-------- .../mih_favourite_businesses.dart | 169 ++++++++++++++---- 2 files changed, 165 insertions(+), 116 deletions(-) diff --git a/Frontend/lib/mih_packages/mzansi_directory/builders/build_favourite_businesses_list.dart b/Frontend/lib/mih_packages/mzansi_directory/builders/build_favourite_businesses_list.dart index 5dcad383..0d0e606e 100644 --- a/Frontend/lib/mih_packages/mzansi_directory/builders/build_favourite_businesses_list.dart +++ b/Frontend/lib/mih_packages/mzansi_directory/builders/build_favourite_businesses_list.dart @@ -1,20 +1,16 @@ import 'package:flutter/material.dart'; import 'package:mzansi_innovation_hub/main.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/arguments.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_objects/bookmarked_business.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_business_profile_preview.dart'; -import 'package:mzansi_innovation_hub/mih_services/mih_business_details_services.dart'; class BuildFavouriteBusinessesList extends StatefulWidget { - final List favouriteBusinesses; + final List favouriteBusinesses; final String? myLocation; - final String? searchQuery; const BuildFavouriteBusinessesList({ super.key, required this.favouriteBusinesses, required this.myLocation, - required this.searchQuery, }); @override @@ -24,28 +20,6 @@ class BuildFavouriteBusinessesList extends StatefulWidget { class _BuildFavouriteBusinessesListState extends State { - List businesses = []; - - List getListOfBusinesses() { - List businesses = []; - for (var item in widget.favouriteBusinesses) { - MihBusinessDetailsServices() - .getBusinessDetailsByBusinessId(item.business_id) - .then((business) { - if (business != null) { - businesses.add(business); - } - }); - } - return businesses; - } - - @override - void initState() { - super.initState(); - businesses = getListOfBusinesses(); - } - @override Widget build(BuildContext context) { return ListView.separated( @@ -58,63 +32,37 @@ class _BuildFavouriteBusinessesListState ); }, itemBuilder: (context, index) { - Future businessDetails = - MihBusinessDetailsServices().getBusinessDetailsByBusinessId( - widget.favouriteBusinesses[index].business_id, - ); - return FutureBuilder( - future: businessDetails, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Row( - children: [ - Padding( - padding: const EdgeInsets.only(left: 25.0), - child: CircularProgressIndicator(), - ), - ], - ); - } else if (snapshot.hasError) { - return Center( - child: Text( - 'Error: ${snapshot.error}', - style: TextStyle(color: Colors.red), + final Business? business = widget.favouriteBusinesses[index]; + + if (business == null) { + return const SizedBox(); // Or a placeholder if a business couldn't be loaded + } + + return Material( + color: MzansiInnovationHub.of(context)!.theme.primaryColor(), + child: InkWell( + onTap: () { + Navigator.of(context).pushNamed( + '/business-profile/view', + arguments: BusinessViewArguments( + business, + business.Name, ), ); - } else if (snapshot.hasData && snapshot.data != null) { - Business business = snapshot.data!; - return Material( - color: MzansiInnovationHub.of(context)!.theme.primaryColor(), - child: InkWell( - onTap: () { - Navigator.of(context).pushNamed( - '/business-profile/view', - arguments: BusinessViewArguments( - business, - business.Name, - ), - ); - }, - splashColor: MzansiInnovationHub.of(context)! - .theme - .secondaryColor() - .withOpacity(0.2), - borderRadius: BorderRadius.circular(15), - child: Padding( - padding: EdgeInsetsGeometry.symmetric( - // vertical: 5, - horizontal: 25, - ), - child: MihBusinessProfilePreview( - business: business, myLocation: widget.myLocation), - ), - ), - ); - } else { - print(snapshot.data); - return SizedBox(); - } - }, + }, + splashColor: MzansiInnovationHub.of(context)! + .theme + .secondaryColor() + .withOpacity(0.2), + borderRadius: BorderRadius.circular(15), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 25, + ), + child: MihBusinessProfilePreview( + business: business, myLocation: widget.myLocation), + ), + ), ); }, ); diff --git a/Frontend/lib/mih_packages/mzansi_directory/package_tools/mih_favourite_businesses.dart b/Frontend/lib/mih_packages/mzansi_directory/package_tools/mih_favourite_businesses.dart index 86a1c261..689182f1 100644 --- a/Frontend/lib/mih_packages/mzansi_directory/package_tools/mih_favourite_businesses.dart +++ b/Frontend/lib/mih_packages/mzansi_directory/package_tools/mih_favourite_businesses.dart @@ -1,11 +1,15 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:mzansi_innovation_hub/main.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/bookmarked_business.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_search_bar.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_single_child_scroll.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/builders/build_favourite_businesses_list.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_business_details_services.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_mzansi_directory_services.dart'; import 'package:supertokens_flutter/supertokens.dart'; @@ -26,33 +30,55 @@ class _MihFavouriteBusinessesState extends State { final FocusNode searchFocusNode = FocusNode(); late Future> boookmarkedBusinessListFuture; List listBookmarkedBusinesses = []; - final ValueNotifier> searchBookmarkedBusinesses = + final ValueNotifier> searchBookmarkedBusinesses = ValueNotifier([]); - Future> getAllBookmarkedBusinessesForUser() async { + late Future> businessDetailsMapFuture; + Map _businessDetailsMap = {}; + Timer? _debounce; + + Future> + getAndMapAllBusinessDetailsForBookmarkedBusinesses() async { String user_id = await SuperTokens.getUserId(); - return MihMzansiDirectoryServices().getAllUserBookmarkedBusiness(user_id); + List bookmarked = await MihMzansiDirectoryServices() + .getAllUserBookmarkedBusiness(user_id); + + // Store the bookmarked list for search filtering + listBookmarkedBusinesses = bookmarked; + + Map businessMap = {}; + List> detailFutures = []; + + for (var item in bookmarked) { + detailFutures.add(MihBusinessDetailsServices() + .getBusinessDetailsByBusinessId(item.business_id)); + } + + List details = await Future.wait(detailFutures); + + for (int i = 0; i < bookmarked.length; i++) { + businessMap[bookmarked[i].business_id] = details[i]; + } + _businessDetailsMap = businessMap; + _filterAndSetBusinesses(); + return businessMap; } - void searchBookmarkedBusinessByName() { - if (businessSearchController.text.isEmpty) { - searchBookmarkedBusinesses.value = listBookmarkedBusinesses; - } else { - List temp = []; - for (var item in listBookmarkedBusinesses) { - if (item.business_name - .toLowerCase() - .contains(businessSearchController.text.toLowerCase())) { - temp.add(item); + void _filterAndSetBusinesses() { + List businessesToDisplay = []; + String query = businessSearchController.text.toLowerCase(); + for (var bookmarked in listBookmarkedBusinesses) { + if (bookmarked.business_name.toLowerCase().contains(query)) { + if (_businessDetailsMap.containsKey(bookmarked.business_id)) { + businessesToDisplay.add(_businessDetailsMap[bookmarked.business_id]); } } - searchBookmarkedBusinesses.value = temp; } + searchBookmarkedBusinesses.value = businessesToDisplay; } @override void dispose() { super.dispose(); - businessSearchController.removeListener(searchBookmarkedBusinessByName); businessSearchController.dispose(); searchFocusNode.dispose(); searchBookmarkedBusinesses.dispose(); @@ -61,8 +87,16 @@ class _MihFavouriteBusinessesState extends State { @override void initState() { super.initState(); - boookmarkedBusinessListFuture = getAllBookmarkedBusinessesForUser(); - businessSearchController.addListener(searchBookmarkedBusinessByName); + businessDetailsMapFuture = + getAndMapAllBusinessDetailsForBookmarkedBusinesses(); + businessSearchController.addListener(() { + if (_debounce?.isActive ?? false) { + _debounce!.cancel(); + } + _debounce = Timer(const Duration(milliseconds: 200), () { + _filterAndSetBusinesses(); + }); + }); } @override @@ -93,8 +127,8 @@ class _MihFavouriteBusinessesState extends State { ), ), const SizedBox(height: 10), - FutureBuilder( - future: boookmarkedBusinessListFuture, + FutureBuilder>( + future: businessDetailsMapFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Mihloadingcircle( @@ -102,18 +136,82 @@ class _MihFavouriteBusinessesState extends State { ); } else if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasData && snapshot.data!.isNotEmpty) { - listBookmarkedBusinesses = snapshot.data!; - searchBookmarkedBusinessByName(); - return ValueListenableBuilder( - valueListenable: searchBookmarkedBusinesses, - builder: (context, value, child) { - return BuildFavouriteBusinessesList( - favouriteBusinesses: value, - myLocation: widget.myLocation, - searchQuery: businessSearchController.text, + // No need to re-filter here, _filterAndSetBusinesses is called in initState + // and by the text controller listener. + return ValueListenableBuilder>( + valueListenable: + searchBookmarkedBusinesses, // Listen to changes in this + builder: (context, businesses, child) { + // Display message if no results after search + if (businesses.isEmpty && + businessSearchController.text.isNotEmpty) { + return Column( + children: [ + const SizedBox(height: 50), + Icon( + Icons + .search_off_rounded, // A different icon for "no results" + size: 150, + color: MzansiInnovationHub.of(context)! + .theme + .secondaryColor(), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10.0), + child: SizedBox( + width: 500, + child: Text( + "No businesses found for '${businessSearchController.text}'", // Specific message for no search results + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], ); - }); + } else if (businesses.isEmpty) { + // Initial empty state + return Column( + children: [ + const SizedBox(height: 50), + Icon( + Icons.business_center_rounded, + size: 150, + color: MzansiInnovationHub.of(context)! + .theme + .secondaryColor(), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10.0), + child: SizedBox( + width: 500, + child: Text( + "No favourites yet, use Mzansi Search to find and bookmark businesses you like", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ); + } + return BuildFavouriteBusinessesList( + favouriteBusinesses: + businesses, // Pass the filtered list from ValueNotifier + myLocation: widget.myLocation, + ); + }, + ); } else { + // This block handles the case where there are no bookmarked businesses initially return Column( children: [ const SizedBox(height: 50), @@ -140,13 +238,16 @@ class _MihFavouriteBusinessesState extends State { ), ], ); - // return Center( - // child: Text("No bookmarked businesses found"), - // ); } - } else { + } else if (snapshot.hasError) { return Center( - child: Text("Error loading bookmarked businesses"), + child: Text( + "Error loading bookmarked businesses: ${snapshot.error}"), // Show specific error + ); + } else { + // Fallback for unexpected states + return Center( + child: Text("An unknown error occurred."), ); } }),