diff --git a/Frontend/lib/main.dart b/Frontend/lib/main.dart index 4f641b32..70a86c1b 100644 --- a/Frontend/lib/main.dart +++ b/Frontend/lib/main.dart @@ -71,8 +71,6 @@ class _MzansiInnovationHubState extends State { double width = MediaQuery.sizeOf(context).width; theme.setScreenType(width); precacheImage(theme.loadingImage(), context); - precacheImage(theme.logoImage(), context); - precacheImage(theme.logoFrame(), context); return MaterialApp( title: getTitle(), themeMode: ThemeMode.dark, diff --git a/Frontend/lib/mih_components/mih_objects/arguments.dart b/Frontend/lib/mih_components/mih_objects/arguments.dart index 3f1cba68..c22ea0d0 100644 --- a/Frontend/lib/mih_components/mih_objects/arguments.dart +++ b/Frontend/lib/mih_components/mih_objects/arguments.dart @@ -229,13 +229,15 @@ class MzansiAiArguments { } class MzansiDirectoryArguments { - final String? startUpSearch; final bool personalSearch; + final int? packageIndex; + final String? startSearchText; - MzansiDirectoryArguments( - this.startUpSearch, - this.personalSearch, - ); + MzansiDirectoryArguments({ + required this.personalSearch, + this.packageIndex, + required this.startSearchText, + }); } class TestArguments { diff --git a/Frontend/lib/mih_components/mih_objects/bookmarked_business.dart b/Frontend/lib/mih_components/mih_objects/bookmarked_business.dart new file mode 100644 index 00000000..9674858e --- /dev/null +++ b/Frontend/lib/mih_components/mih_objects/bookmarked_business.dart @@ -0,0 +1,35 @@ +class BookmarkedBusiness { + final int idbookmarked_businesses; + final String app_id; + final String business_id; + final String business_name; + final String created_date; + + BookmarkedBusiness({ + required this.idbookmarked_businesses, + required this.app_id, + required this.business_id, + required this.business_name, + required this.created_date, + }); + factory BookmarkedBusiness.fromJson(Map json) { + return switch (json) { + { + "idbookmarked_businesses": int idbookmarked_businesses, + "app_id": String app_id, + "business_id": String business_id, + "business_name": String business_name, + "created_date": String created_date, + } => + BookmarkedBusiness( + idbookmarked_businesses: idbookmarked_businesses, + app_id: app_id, + business_id: business_id, + business_name: business_name, + created_date: created_date, + ), + _ => throw const FormatException( + 'Failed to load bookmarked business objects'), + }; + } +} diff --git a/Frontend/lib/mih_components/mih_objects/business_review.dart b/Frontend/lib/mih_components/mih_objects/business_review.dart index 5aed0bd9..d4fe583c 100644 --- a/Frontend/lib/mih_components/mih_objects/business_review.dart +++ b/Frontend/lib/mih_components/mih_objects/business_review.dart @@ -40,7 +40,8 @@ class BusinessReview { date_time: date_time, reviewer: reviewer, ), - _ => throw const FormatException('Failed to load loyalty card objects'), + _ => + throw const FormatException('Failed to load Business Review objects'), }; } } diff --git a/Frontend/lib/mih_components/mih_package_components/Example/package_tools/package_tool_one.dart b/Frontend/lib/mih_components/mih_package_components/Example/package_tools/package_tool_one.dart index 6195fabd..85006f32 100644 --- a/Frontend/lib/mih_components/mih_package_components/Example/package_tools/package_tool_one.dart +++ b/Frontend/lib/mih_components/mih_package_components/Example/package_tools/package_tool_one.dart @@ -9,6 +9,7 @@ import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_ import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_business_profile_preview.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_personal_profile_preview.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; +import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/components/mih_business_info_card.dart'; // import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/components/mih_business_info_card.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_alert_services.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_location_services.dart'; @@ -30,6 +31,7 @@ import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_ import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_text_form_field.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_time_field.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_toggle.dart'; +import 'package:redacted/redacted.dart'; class PackageToolOne extends StatefulWidget { final AppUser user; @@ -291,16 +293,34 @@ class _PackageToolOneState extends State { } }), const SizedBox(height: 10), - // MihBusinessCard( - // businessid: "123456", - // businessName: "Mzansi Innovation Hub", - // cellNumber: "0788300006", - // email: "yasien.meth@mzansi-innovation-hub.co.za", - // gpsLocation: "-26.1853611, 28.134664", - // website: - // "https://app.mzansi-innovation-hub.co.za/privacy.html", - // rating: 3.25, - // ), + Text("This text should be redacted").redacted( + context: context, + redact: true, + ), + MihBusinessCard( + business: Business( + "business_id", + "Name", + "type", + "registration_no", + "logo_name", + "logo_path", + "contact_no", + "bus_email", + "app_id", + "gps_location", + "practice_no", + "vat_no", + "website", + "rating", + "mission_vision", + ), + startUpSearch: '', + width: 300, + ).redacted( + context: context, + redact: true, + ), const SizedBox(height: 10), Divider( color: diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/frame_dark.png b/Frontend/lib/mih_components/mih_package_components/assets/images/frame_dark.png deleted file mode 100644 index 05756dbb..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/frame_dark.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/frame_light.png b/Frontend/lib/mih_components/mih_package_components/assets/images/frame_light.png deleted file mode 100644 index 00b0a359..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/frame_light.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/i-dont-know-dark.png b/Frontend/lib/mih_components/mih_package_components/assets/images/i-dont-know-dark.png deleted file mode 100644 index a9e5cff9..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/i-dont-know-dark.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/i-dont-know-light.png b/Frontend/lib/mih_components/mih_package_components/assets/images/i-dont-know-light.png deleted file mode 100644 index 69e2ea5b..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/i-dont-know-light.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/logo_dark.png b/Frontend/lib/mih_components/mih_package_components/assets/images/logo_dark.png deleted file mode 100644 index 1efb415d..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/logo_dark.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/Clicks_Club.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/Clicks_Club.png deleted file mode 100644 index c6f22e50..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/Clicks_Club.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/Shell.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/Shell.png deleted file mode 100644 index 7f4b2f02..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/Shell.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/apple_tree.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/apple_tree.png deleted file mode 100644 index 15f09090..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/apple_tree.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/best_before.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/best_before.png deleted file mode 100644 index 92951bf9..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/best_before.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/bp.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/bp.png deleted file mode 100644 index ed59ecc6..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/bp.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/builders.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/builders.png deleted file mode 100644 index 4fbdc237..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/builders.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/checkers.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/checkers.png deleted file mode 100644 index 2e0fda82..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/checkers.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/checkers_xtra.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/checkers_xtra.png deleted file mode 100644 index e1b46238..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/checkers_xtra.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/cotton_on_perks.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/cotton_on_perks.png deleted file mode 100644 index 3bc76756..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/cotton_on_perks.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/dischem_benefit.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/dischem_benefit.png deleted file mode 100644 index 0130f30d..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/dischem_benefit.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/edgars.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/edgars.png deleted file mode 100644 index 7bf048cb..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/edgars.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/eskom.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/eskom.png deleted file mode 100644 index 4826ba97..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/eskom.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/exclusive_books.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/exclusive_books.png deleted file mode 100644 index b9136c2e..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/exclusive_books.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/fresh_stop.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/fresh_stop.png deleted file mode 100644 index acfd7d97..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/fresh_stop.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/infinity.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/infinity.png deleted file mode 100644 index 6d7ec060..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/infinity.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/jet.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/jet.png deleted file mode 100644 index 01412cbc..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/jet.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/leroy_merlin.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/leroy_merlin.png deleted file mode 100644 index 0d3c8f55..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/leroy_merlin.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/makro.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/makro.png deleted file mode 100644 index 0e42cb46..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/makro.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/mini/total_energies-min.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/mini/total_energies-min.png new file mode 100644 index 00000000..a33f5250 Binary files /dev/null and b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/mini/total_energies-min.png differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/panarottis.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/panarottis.png deleted file mode 100644 index 79c5c0ca..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/panarottis.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/plus_more.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/plus_more.png deleted file mode 100644 index 89d46bce..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/plus_more.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/pna.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/pna.png deleted file mode 100644 index 53bcc73d..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/pna.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/pnp_smart.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/pnp_smart.png deleted file mode 100644 index 6c232c74..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/pnp_smart.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/pq.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/pq.png deleted file mode 100644 index ef855d39..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/pq.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/rage.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/rage.png deleted file mode 100644 index 5a41398f..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/rage.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/sasol.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/sasol.png deleted file mode 100644 index a0025408..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/sasol.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/shoprite.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/shoprite.png deleted file mode 100644 index a4c9fe27..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/shoprite.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/shoprite_xtra.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/shoprite_xtra.png deleted file mode 100644 index 890399e1..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/shoprite_xtra.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/signature_cosmetics.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/signature_cosmetics.png deleted file mode 100644 index 8d87ba5a..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/signature_cosmetics.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/spar_rewards.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/spar_rewards.png deleted file mode 100644 index 04dad579..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/spar_rewards.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/spur.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/spur.png deleted file mode 100644 index 7bae5cc6..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/spur.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/tfg.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/tfg.png deleted file mode 100644 index 3cd038fb..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/tfg.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/toysrus.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/toysrus.png deleted file mode 100644 index 9c8756d3..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/toysrus.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/wrewards.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/wrewards.png deleted file mode 100644 index 3822997b..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/wrewards.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/xtraSavings.png b/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/xtraSavings.png deleted file mode 100644 index 8faa5b04..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/loyalty_cards/xtraSavings.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/mzansi_ai-dark.png b/Frontend/lib/mih_components/mih_package_components/assets/images/mzansi_ai-dark.png deleted file mode 100644 index d99581d4..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/mzansi_ai-dark.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/mzansi_ai-light.png b/Frontend/lib/mih_components/mih_package_components/assets/images/mzansi_ai-light.png deleted file mode 100644 index 30658141..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/mzansi_ai-light.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/assets/images/notify_logo.png b/Frontend/lib/mih_components/mih_package_components/assets/images/notify_logo.png deleted file mode 100644 index 1041552c..00000000 Binary files a/Frontend/lib/mih_components/mih_package_components/assets/images/notify_logo.png and /dev/null differ diff --git a/Frontend/lib/mih_components/mih_package_components/mih_package.dart b/Frontend/lib/mih_components/mih_package_components/mih_package.dart index f2727065..f7718a09 100644 --- a/Frontend/lib/mih_components/mih_package_components/mih_package.dart +++ b/Frontend/lib/mih_components/mih_package_components/mih_package.dart @@ -44,7 +44,7 @@ class _MihPackageState extends State int nextPage = currentPage + 1 < widget.appBody.length ? currentPage + 1 : currentPage; if (nextPage != currentPage) { - await Future.delayed(const Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 100)); await _pageController.animateTo( currentOffset + peakOffset, duration: const Duration(milliseconds: 300), @@ -86,10 +86,24 @@ class _MihPackageState extends State vsync: this, duration: const Duration(milliseconds: 400), ); + // if (!MzansiInnovationHub.of(context)!.theme.kIsWeb) { + // // Trigger the peak animation on start (or call this elsewhere) + // WidgetsBinding.instance.addPostFrameCallback((_) { + // _peakAnimation(); + // }); + // } if (!MzansiInnovationHub.of(context)!.theme.kIsWeb) { - // Trigger the peak animation on start (or call this elsewhere) + // Trigger the peak animation only AFTER the route transition is complete WidgetsBinding.instance.addPostFrameCallback((_) { - _peakAnimation(); + final ModalRoute? currentRoute = ModalRoute.of(context); + if (currentRoute != null) { + currentRoute.animation?.addStatusListener((status) { + if (status == AnimationStatus.completed && mounted) { + // Ensure the widget is still mounted and the animation is completed + _peakAnimation(); + } + }); + } }); } } diff --git a/Frontend/lib/mih_components/mih_package_components/mih_search_bar.dart b/Frontend/lib/mih_components/mih_package_components/mih_search_bar.dart index 0bc691f5..61d0f386 100644 --- a/Frontend/lib/mih_components/mih_package_components/mih_search_bar.dart +++ b/Frontend/lib/mih_components/mih_package_components/mih_search_bar.dart @@ -109,6 +109,7 @@ class _MihSearchBarState extends State { color: widget.fillColor, child: AnimatedContainer( // Keep AnimatedContainer for width/height transitions + alignment: Alignment.centerLeft, width: widget.width, height: widget.height ?? 50, duration: const Duration(milliseconds: 300), @@ -123,6 +124,7 @@ class _MihSearchBarState extends State { ), ), child: TextField( + textAlignVertical: TextAlignVertical.center, controller: widget.controller, // Assign the controller focusNode: widget.searchFocusNode, autocorrect: true, @@ -134,17 +136,20 @@ class _MihSearchBarState extends State { style: TextStyle( color: widget.hintColor, fontWeight: FontWeight.w600, + fontSize: 16, ), cursorColor: widget.hintColor, decoration: InputDecoration( + isDense: true, hintText: widget.hintText, hintStyle: TextStyle( color: widget.hintColor, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w600, + fontSize: 16, ), border: InputBorder.none, contentPadding: - const EdgeInsets.symmetric(horizontal: 20.0, vertical: 12.0), + const EdgeInsets.symmetric(horizontal: 10.0, vertical: 15.0), prefixIcon: GestureDetector( onTap: widget.onPrefixIconTap, child: getPrefixIcon(), @@ -152,12 +157,15 @@ class _MihSearchBarState extends State { suffixIcon: Row( // Use a Row for multiple suffix icons mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ // Optional suffix tools if (widget.suffixTools != null) ...widget.suffixTools!, // Clear Icon (conditionally visible) if (_showClearIcon) // Only show if input is not empty IconButton( + iconSize: 35, icon: Icon(Icons.clear, color: widget.hintColor), // Clear icon onPressed: widget.onClearIconTap ?? diff --git a/Frontend/lib/mih_config/mih_theme.dart b/Frontend/lib/mih_config/mih_theme.dart index b378d0cc..c241935a 100644 --- a/Frontend/lib/mih_config/mih_theme.dart +++ b/Frontend/lib/mih_config/mih_theme.dart @@ -163,54 +163,6 @@ class MihTheme { } } - AssetImage logoFrame() { - if (mode == "Dark") { - return const AssetImage( - 'lib/mih_components/mih_package_components/assets/images/frame_dark.png', - ); - } else { - return const AssetImage( - 'lib/mih_components/mih_package_components/assets/images/frame_light.png', - ); - } - } - - AssetImage altLogoFrame() { - if (mode == "Light") { - return const AssetImage( - 'lib/mih_components/mih_package_components/assets/images/frame_dark.png', - ); - } else { - return const AssetImage( - 'lib/mih_components/mih_package_components/assets/images/frame_light.png', - ); - } - } - - AssetImage logoImage() { - if (mode == "Dark") { - return const AssetImage( - 'lib/mih_components/mih_package_components/assets/images/logo_dark.png', - ); - } else { - return const AssetImage( - 'lib/mih_components/mih_package_components/assets/images/logo_light.png', - ); - } - } - - AssetImage altLogoImage() { - if (mode == "Light") { - return const AssetImage( - 'lib/mih_components/mih_package_components/assets/images/logo_dark.png', - ); - } else { - return const AssetImage( - 'lib/mih_components/mih_package_components/assets/images/logo_light.png', - ); - } - } - AssetImage loadingImage() { if (mode == "Dark") { loading = const AssetImage( diff --git a/Frontend/lib/mih_packages/about_mih/package_tools/mih_info.dart b/Frontend/lib/mih_packages/about_mih/package_tools/mih_info.dart index ed4f9dd8..ebe8eba4 100644 --- a/Frontend/lib/mih_packages/about_mih/package_tools/mih_info.dart +++ b/Frontend/lib/mih_packages/about_mih/package_tools/mih_info.dart @@ -47,8 +47,6 @@ class _MihInfoState extends State { bio += "(University of the Western Cape)\n"; bio += "6 Year of banking experience with one of the big 5 banks of South Africa."; - ImageProvider logoFrame = - MzansiInnovationHub.of(context)!.theme.altLogoFrame(); return Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, @@ -61,18 +59,22 @@ class _MihInfoState extends State { alignment: Alignment.center, fit: StackFit.loose, children: [ - CircleAvatar( - backgroundColor: - MzansiInnovationHub.of(context)!.theme.primaryColor(), - backgroundImage: const AssetImage( - "lib/mih_components/mih_package_components/assets/images/founder.jpg"), - //'https://media.licdn.com/dms/image/D4D03AQGd1-QhjtWWpA/profile-displayphoto-shrink_400_400/0/1671698053061?e=2147483647&v=beta&t=a3dJI5yxs5-KeXjj10LcNCFuC9IOfa8nNn3k_Qyr0CA'), - radius: 75, + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: CircleAvatar( + backgroundColor: + MzansiInnovationHub.of(context)!.theme.primaryColor(), + backgroundImage: const AssetImage( + "lib/mih_components/mih_package_components/assets/images/founder.jpg"), + //'https://media.licdn.com/dms/image/D4D03AQGd1-QhjtWWpA/profile-displayphoto-shrink_400_400/0/1671698053061?e=2147483647&v=beta&t=a3dJI5yxs5-KeXjj10LcNCFuC9IOfa8nNn3k_Qyr0CA'), + radius: 75, + ), + ), + Icon( + MihIcons.mihRing, + size: 165, + color: MzansiInnovationHub.of(context)!.theme.secondaryColor(), ), - SizedBox( - width: 165, - child: Image(image: logoFrame), - ) ], ), ), diff --git a/Frontend/lib/mih_packages/mih_home/components/mih_app_drawer.dart b/Frontend/lib/mih_packages/mih_home/components/mih_app_drawer.dart index a30d3c0f..5081a062 100644 --- a/Frontend/lib/mih_packages/mih_home/components/mih_app_drawer.dart +++ b/Frontend/lib/mih_packages/mih_home/components/mih_app_drawer.dart @@ -61,35 +61,6 @@ class _MIHAppDrawerState extends State { backgroundColor: MzansiInnovationHub.of(context)!.theme.secondaryColor(), ), - // MIHProfilePicture( - // profilePictureFile: widget.propicFile, - // proPicController: proPicController, - // proPic: null, - // width: 60, - // radius: 27, - // drawerMode: true, - // editable: false, - // frameColor: MzansiInnovationHub.of(context)!.theme.primaryColor(), - // onChange: (newProPic) {}, - // ), - - // Stack( - // alignment: Alignment.center, - // fit: StackFit.loose, - // children: [ - // CircleAvatar( - // backgroundColor: - // MzansiInnovationHub.of(context)!.theme.primaryColor(), - // backgroundImage: widget.propicFile, - // //'https://media.licdn.com/dms/image/D4D03AQGd1-QhjtWWpA/profile-displayphoto-shrink_400_400/0/1671698053061?e=2147483647&v=beta&t=a3dJI5yxs5-KeXjj10LcNCFuC9IOfa8nNn3k_Qyr0CA'), - // radius: 27, - // ), - // SizedBox( - // width: 60, - // child: Image(image: logoFrame), - // ) - // ], - // ), ); } diff --git a/Frontend/lib/mih_packages/mih_home/mih_home_legacy.dart b/Frontend/lib/mih_packages/mih_home/mih_home_legacy.dart index d6639fcb..e15c58c2 100644 --- a/Frontend/lib/mih_packages/mih_home/mih_home_legacy.dart +++ b/Frontend/lib/mih_packages/mih_home/mih_home_legacy.dart @@ -86,24 +86,22 @@ class _MIHHomeLegacyState extends State { // ); void setAppsNewPersonal(List tileList) { - ImageProvider logo = MzansiInnovationHub.of(context)!.theme.logoImage(); - if (widget.signedInUser.fname == "") { - tileList.add(MIHTile( - videoID: "jFV3NN65DtQ", - onTap: () { - Navigator.of(context).pushNamed('/mzansi-profile', - arguments: AppProfileUpdateArguments( - widget.signedInUser, widget.propicFile)); - }, - tileName: "Setup Profie", - tileIcon: Padding( - padding: const EdgeInsets.all(15.0), - child: Image(image: logo), - ), - p: getPrim(), - s: getSec(), - )); + // tileList.add(MIHTile( + // videoID: "jFV3NN65DtQ", + // onTap: () { + // Navigator.of(context).pushNamed('/mzansi-profile', + // arguments: AppProfileUpdateArguments( + // widget.signedInUser, widget.propicFile)); + // }, + // tileName: "Setup Profie", + // tileIcon: Padding( + // padding: const EdgeInsets.all(15.0), + // child: Image(image: logo), + // ), + // p: getPrim(), + // s: getSec(), + // )); } } @@ -128,27 +126,26 @@ class _MIHHomeLegacyState extends State { } void setAppsPersonal(List tileList) { - ImageProvider logo = MzansiInnovationHub.of(context)!.theme.logoImage(); ImageProvider aiLogo = MzansiInnovationHub.of(context)!.theme.aiLogoImage(); - tileList.add(MIHTile( - videoID: "P2bM9eosJ_A", - onTap: () { - Navigator.of(context).pushNamed( - '/mzansi-profile', - arguments: AppProfileUpdateArguments( - widget.signedInUser, - widget.propicFile, - ), - ); - }, - tileName: "Mzansi Profile", - tileIcon: Padding( - padding: const EdgeInsets.all(15.0), - child: Image(image: logo), - ), - p: getPrim(), - s: getSec(), - )); + // tileList.add(MIHTile( + // videoID: "P2bM9eosJ_A", + // onTap: () { + // Navigator.of(context).pushNamed( + // '/mzansi-profile', + // arguments: AppProfileUpdateArguments( + // widget.signedInUser, + // widget.propicFile, + // ), + // ); + // }, + // tileName: "Mzansi Profile", + // tileIcon: Padding( + // padding: const EdgeInsets.all(15.0), + // child: Image(image: logo), + // ), + // p: getPrim(), + // s: getSec(), + // )); tileList.add(MIHTile( videoID: "6l8h0sjt08k", onTap: () { 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 new file mode 100644 index 00000000..0d0e606e --- /dev/null +++ b/Frontend/lib/mih_packages/mzansi_directory/builders/build_favourite_businesses_list.dart @@ -0,0 +1,70 @@ +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/business.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_business_profile_preview.dart'; + +class BuildFavouriteBusinessesList extends StatefulWidget { + final List favouriteBusinesses; + final String? myLocation; + const BuildFavouriteBusinessesList({ + super.key, + required this.favouriteBusinesses, + required this.myLocation, + }); + + @override + State createState() => + _BuildFavouriteBusinessesListState(); +} + +class _BuildFavouriteBusinessesListState + extends State { + @override + Widget build(BuildContext context) { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.favouriteBusinesses.length, + separatorBuilder: (BuildContext context, index) { + return Divider( + color: Theme.of(context).colorScheme.secondary, + ); + }, + itemBuilder: (context, index) { + 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, + ), + ); + }, + 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/mzansi_directory.dart b/Frontend/lib/mih_packages/mzansi_directory/mzansi_directory.dart index 3ddc5e55..8b48989b 100644 --- a/Frontend/lib/mih_packages/mzansi_directory/mzansi_directory.dart +++ b/Frontend/lib/mih_packages/mzansi_directory/mzansi_directory.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/arguments.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_action.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tools.dart'; +import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/package_tools/mih_favourite_businesses.dart'; import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/package_tools/mih_search_mzansi.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_location_services.dart'; class MzansiDirectory extends StatefulWidget { final MzansiDirectoryArguments arguments; @@ -18,9 +21,22 @@ class MzansiDirectory extends StatefulWidget { class _MzansiDirectoryState extends State { int _selcetedIndex = 0; + late Future futurePosition = + MIHLocationAPI().getGPSPosition(context); + + @override + void initState() { + super.initState(); + if (widget.arguments.packageIndex == null) { + _selcetedIndex = 0; + } else { + _selcetedIndex = widget.arguments.packageIndex!; + } + } @override Widget build(BuildContext context) { + print('MzansiDirectory build method called!'); return MihPackage( appActionButton: getAction(), appTools: getTools(), @@ -37,12 +53,41 @@ class _MzansiDirectoryState extends State { List getToolBody() { List toolBodies = [ - MihSearchMzansi( - startUpSearch: widget.arguments.startUpSearch, - personalSearch: widget.arguments.personalSearch, - ), + FutureBuilder( + future: futurePosition, + builder: (context, asyncSnapshot) { + String myLocation = ""; + if (asyncSnapshot.connectionState == ConnectionState.waiting) { + myLocation = "Getting Your GPS Location Ready"; + } else { + myLocation = asyncSnapshot.data + .toString() + .replaceAll("Latitude: ", "") + .replaceAll("Longitude: ", ""); + } + return MihSearchMzansi( + personalSearch: widget.arguments.personalSearch, + myLocation: myLocation, + startSearchText: widget.arguments.startSearchText, + ); + }), // MihContacts(), - // MihFavouriteBusinesses(), + FutureBuilder( + future: futurePosition, + builder: (context, asyncSnapshot) { + String myLocation = ""; + if (asyncSnapshot.connectionState == ConnectionState.waiting) { + myLocation = "Getting Your GPS Location Ready"; + } else { + myLocation = asyncSnapshot.data + .toString() + .replaceAll("Latitude: ", "") + .replaceAll("Longitude: ", ""); + } + return MihFavouriteBusinesses( + myLocation: myLocation, + ); + }), ]; return toolBodies; } @@ -70,11 +115,11 @@ class _MzansiDirectoryState extends State { // _selcetedIndex = 1; // }); // }; - // temp[const Icon(Icons.business_center)] = () { - // setState(() { - // _selcetedIndex = 2; - // }); - // }; + temp[const Icon(Icons.business_center)] = () { + setState(() { + _selcetedIndex = 1; + }); + }; return MihPackageTools( tools: temp, selcetedIndex: _selcetedIndex, @@ -84,8 +129,8 @@ class _MzansiDirectoryState extends State { List getToolTitle() { List toolTitles = [ "Mzansi Search", - "Contacts", "Favourite Businesses", + "Contacts", ]; return toolTitles; } diff --git a/Frontend/lib/mih_packages/mzansi_directory/package_tiles/mzansi_directory_tile.dart b/Frontend/lib/mih_packages/mzansi_directory/package_tiles/mzansi_directory_tile.dart index d74b46ab..e19c7916 100644 --- a/Frontend/lib/mih_packages/mzansi_directory/package_tiles/mzansi_directory_tile.dart +++ b/Frontend/lib/mih_packages/mzansi_directory/package_tiles/mzansi_directory_tile.dart @@ -23,8 +23,8 @@ class _MzansiDirectoryTileState extends State { Navigator.of(context).pushNamed( '/mzansi-directory', arguments: MzansiDirectoryArguments( - null, // startUpSearch - true, // personalSearch + personalSearch: true, + startSearchText: null, ), ); }, 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 76c53e22..fb018a73 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,24 @@ +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'; class MihFavouriteBusinesses extends StatefulWidget { - const MihFavouriteBusinesses({super.key}); + final String? myLocation; + const MihFavouriteBusinesses({ + super.key, + required this.myLocation, + }); @override State createState() => _MihFavouriteBusinessesState(); @@ -15,6 +28,70 @@ class _MihFavouriteBusinessesState extends State { final TextEditingController businessSearchController = TextEditingController(); final FocusNode searchFocusNode = FocusNode(); + late Future> boookmarkedBusinessListFuture; + List listBookmarkedBusinesses = []; + final ValueNotifier> searchBookmarkedBusinesses = + ValueNotifier([]); + late Future> businessDetailsMapFuture; + Map _businessDetailsMap = {}; + Timer? _debounce; + + Future> + getAndMapAllBusinessDetailsForBookmarkedBusinesses() async { + String user_id = await SuperTokens.getUserId(); + List bookmarked = await MihMzansiDirectoryServices() + .getAllUserBookmarkedBusiness(user_id); + 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 _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 = businessesToDisplay; + } + + @override + void dispose() { + super.dispose(); + businessSearchController.dispose(); + searchFocusNode.dispose(); + searchBookmarkedBusinesses.dispose(); + } + + @override + void initState() { + super.initState(); + businessDetailsMapFuture = + getAndMapAllBusinessDetailsForBookmarkedBusinesses(); + businessSearchController.addListener(() { + if (_debounce?.isActive ?? false) { + _debounce!.cancel(); + } + _debounce = Timer(const Duration(milliseconds: 200), () { + _filterAndSetBusinesses(); + }); + }); + } @override Widget build(BuildContext context) { @@ -44,6 +121,130 @@ class _MihFavouriteBusinessesState extends State { ), ), const SizedBox(height: 10), + FutureBuilder>( + future: businessDetailsMapFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Mihloadingcircle( + message: "Getting your favourites", + ); + } else if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasData && snapshot.data!.isNotEmpty) { + // 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), + 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, + ), + ), + ), + ), + ], + ); + } + } else if (snapshot.hasError) { + return Center( + child: Text( + "Error loading bookmarked businesses: ${snapshot.error}"), // Show specific error + ); + } else { + // Fallback for unexpected states + return Center( + child: Text("An unknown error occurred."), + ); + } + }), ], ), ); diff --git a/Frontend/lib/mih_packages/mzansi_directory/package_tools/mih_search_mzansi.dart b/Frontend/lib/mih_packages/mzansi_directory/package_tools/mih_search_mzansi.dart index e4a546eb..714f20a8 100644 --- a/Frontend/lib/mih_packages/mzansi_directory/package_tools/mih_search_mzansi.dart +++ b/Frontend/lib/mih_packages/mzansi_directory/package_tools/mih_search_mzansi.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:geolocator/geolocator.dart'; import 'package:mzansi_innovation_hub/main.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/app_user.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_dropdwn_field.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_icons.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'; @@ -10,17 +11,19 @@ import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_ 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_business_search_resultsList.dart'; import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/builders/build_user_search_results_list.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_alert_services.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_business_details_services.dart'; -import 'package:mzansi_innovation_hub/mih_services/mih_location_services.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_user_services.dart'; class MihSearchMzansi extends StatefulWidget { - final String? startUpSearch; final bool personalSearch; + final String? myLocation; + final String? startSearchText; const MihSearchMzansi({ super.key, - required this.startUpSearch, required this.personalSearch, + required this.myLocation, + required this.startSearchText, }); @override @@ -29,14 +32,62 @@ class MihSearchMzansi extends StatefulWidget { class _MihSearchMzansiState extends State { final TextEditingController mzansiSearchController = TextEditingController(); + final TextEditingController businessTypeController = TextEditingController(); final FocusNode searchFocusNode = FocusNode(); late bool userSearch; Future?> futureUserSearchResults = Future.value(); Future?> futureBusinessSearchResults = Future.value(); - late Future futurePosition = - MIHLocationAPI().getGPSPosition(context); List userSearchResults = []; List businessSearchResults = []; + late Future> availableBusinessTypes; + bool filterOn = false; + + void swapPressed() { + setState(() { + userSearch = !userSearch; + if (filterOn) { + filterOn = !filterOn; + } + }); + if (businessTypeController.text.isNotEmpty) { + setState(() { + futureBusinessSearchResults = Future.value(); + businessTypeController.clear(); + }); + } + searchPressed(); + } + + void clearAll() { + setState(() { + futureUserSearchResults = Future.value(); + futureBusinessSearchResults = Future.value(); + mzansiSearchController.clear(); + businessTypeController.clear(); + }); + } + + void searchPressed() { + setState(() { + // userSearch = !userSearch; + if (userSearch && mzansiSearchController.text.isNotEmpty) { + futureUserSearchResults = + MihUserServices().searchUsers(mzansiSearchController.text, context); + } else { + if ( + // mzansiSearchController.text.isNotEmpty && + businessTypeController.text.isNotEmpty) { + futureBusinessSearchResults = MihBusinessDetailsServices() + .searchBusinesses(mzansiSearchController.text, + businessTypeController.text, context); + } else if (mzansiSearchController.text.isNotEmpty) { + futureBusinessSearchResults = MihBusinessDetailsServices() + .searchBusinesses(mzansiSearchController.text, + businessTypeController.text, context); + } + } + }); + } @override void dispose() { @@ -49,13 +100,13 @@ class _MihSearchMzansiState extends State { super.initState(); setState(() { userSearch = widget.personalSearch; - mzansiSearchController.text = widget.startUpSearch ?? ""; - if (userSearch) { - futureUserSearchResults = - MihUserServices().searchUsers(mzansiSearchController.text, context); + availableBusinessTypes = + MihBusinessDetailsServices().fetchAllBusinessTypes(); + if (widget.startSearchText != null) { + mzansiSearchController.text = widget.startSearchText!; + searchPressed(); } else { - futureBusinessSearchResults = MihBusinessDetailsServices() - .searchBusinesses(mzansiSearchController.text, context); + mzansiSearchController.text = ""; } }); } @@ -76,76 +127,132 @@ class _MihSearchMzansiState extends State { children: [ Padding( padding: EdgeInsets.symmetric(horizontal: width / 20), - child: MihSearchBar( - controller: mzansiSearchController, - hintText: "Search Mzansi", - prefixIcon: Icons.search, - prefixAltIcon: userSearch ? Icons.person : Icons.business, - suffixTools: [ - IconButton( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: MihSearchBar( + controller: mzansiSearchController, + hintText: "Search Mzansi", + prefixIcon: Icons.search, + prefixAltIcon: userSearch ? Icons.person : Icons.business, + suffixTools: [ + IconButton( + onPressed: () { + swapPressed(); + }, + icon: Icon( + Icons.swap_horiz_rounded, + size: 35, + color: MzansiInnovationHub.of(context)! + .theme + .primaryColor(), + ), + ), + ], + fillColor: + MzansiInnovationHub.of(context)!.theme.secondaryColor(), + hintColor: + MzansiInnovationHub.of(context)!.theme.primaryColor(), + onPrefixIconTap: () { + searchPressed(); + }, + onClearIconTap: () { + clearAll(); + }, + searchFocusNode: searchFocusNode, + ), + ), + Visibility( + visible: !userSearch, + child: const SizedBox(width: 10), + ), + Visibility( + visible: !userSearch, + child: IconButton( onPressed: () { + if (filterOn) { + clearAll(); + } setState(() { - // searchTypeVisibility = !searchTypeVisibility; - userSearch = !userSearch; - if (userSearch) { - futureUserSearchResults = MihUserServices() - .searchUsers( - mzansiSearchController.text, context); - } else { - futureBusinessSearchResults = - MihBusinessDetailsServices().searchBusinesses( - mzansiSearchController.text, context); - } + filterOn = !filterOn; }); }, icon: Icon( - Icons.swap_horiz_rounded, + !filterOn + ? Icons.filter_list_rounded + : Icons.filter_list_off_rounded, size: 35, - color: - MzansiInnovationHub.of(context)!.theme.primaryColor(), - )) + color: MzansiInnovationHub.of(context)! + .theme + .secondaryColor(), + ), + ), + ), ], - fillColor: - MzansiInnovationHub.of(context)!.theme.secondaryColor(), - hintColor: MzansiInnovationHub.of(context)!.theme.primaryColor(), - onPrefixIconTap: () { - if (userSearch) { - setState(() { - futureUserSearchResults = MihUserServices() - .searchUsers(mzansiSearchController.text, context); - }); - } else { - setState(() { - futureBusinessSearchResults = MihBusinessDetailsServices() - .searchBusinesses(mzansiSearchController.text, context); - }); - } - }, - onClearIconTap: () { - setState(() { - futureUserSearchResults = Future.value(); - futureBusinessSearchResults = Future.value(); - mzansiSearchController.clear(); - }); - }, - searchFocusNode: searchFocusNode, ), ), const SizedBox(height: 10), FutureBuilder( - future: futurePosition, + future: availableBusinessTypes, builder: (context, asyncSnapshot) { - String myLocation = ""; - if (asyncSnapshot.connectionState == ConnectionState.waiting) { - myLocation = "Getting Your GPS Location Ready"; - } else { - myLocation = asyncSnapshot.data - .toString() - .replaceAll("Latitude: ", "") - .replaceAll("Longitude: ", ""); + List options = []; + if (asyncSnapshot.connectionState == ConnectionState.done) { + options.addAll(asyncSnapshot.data!); } - return displaySearchResults(userSearch, myLocation); + return Visibility( + visible: filterOn, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: width / 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: MihDropdownField( + controller: businessTypeController, + hintText: "Business Type Filter", + dropdownOptions: options, + requiredText: true, + editable: true, + enableSearch: true, + ), + ), + const SizedBox(width: 10), + MihButton( + onPressed: () { + if (businessTypeController.text.isNotEmpty) { + searchPressed(); + } else { + MihAlertServices().errorAlert( + "Business Type Not Selected", + "Please ensure you have selected a Business Type before seareching for Businesses of Mzansi", + context, + ); + } + }, + buttonColor: MzansiInnovationHub.of(context)! + .theme + .successColor(), + elevation: 10, + child: Text( + "Search", + style: TextStyle( + color: MzansiInnovationHub.of(context)! + .theme + .primaryColor(), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ); }), + const SizedBox(height: 10), + displaySearchResults(userSearch, widget.myLocation ?? ""), ], ), ); diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_add_bookmark_alert.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_add_bookmark_alert.dart new file mode 100644 index 00000000..565f8910 --- /dev/null +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_add_bookmark_alert.dart @@ -0,0 +1,126 @@ +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/business.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_alert.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; +import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_alert_services.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_mzansi_directory_services.dart'; +import 'package:supertokens_flutter/supertokens.dart'; + +class MihAddBookmarkAlert extends StatefulWidget { + final Business business; + const MihAddBookmarkAlert({ + super.key, + required this.business, + }); + + @override + State createState() => _MihAddBookmarkAlertState(); +} + +class _MihAddBookmarkAlertState extends State { + Future addBookmark(String business_id) async { + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + String user_id = await SuperTokens.getUserId(); + await MihMzansiDirectoryServices() + .addBookmarkedBusiness(user_id, business_id) + .then((statusCode) { + if (statusCode == 201) { + Navigator.of(context).pushNamedAndRemoveUntil( + '/mzansi-directory', + ModalRoute.withName('/'), + arguments: MzansiDirectoryArguments( + personalSearch: false, // personalSearch + packageIndex: 1, + startSearchText: widget.business.Name, + ), + ); + MihAlertServices().successAlert( + "Successfully Bookmarked Business!", + "${widget.business.Name} has successfully been added to favourite businessess in the Mzansi Directory.", + context, + ); + } else { + Navigator.of(context).pop(); + MihAlertServices().errorAlert( + "Error Adding Bookmark", + "An error occured while add ${widget.business.Name} to you Mzansi Directory, Please try again later.", + context, + ); + } + }); + } + + @override + Widget build(BuildContext context) { + return MihPackageAlert( + alertColour: MihColors.getSecondaryColor(context), + alertIcon: Icon( + Icons.warning_rounded, + size: 100, + color: MihColors.getSecondaryColor(context), + ), + alertTitle: "Bookmark Business", + alertBody: Column( + children: [ + Text( + "Are you sure you want to save ${widget.business.Name} to your Mzansi Directory?", + style: TextStyle( + color: MzansiInnovationHub.of(context)!.theme.secondaryColor(), + fontSize: 15, + ), + ), + const SizedBox(height: 25), + Wrap( + spacing: 10, + runSpacing: 10, + children: [ + MihButton( + width: 300, + onPressed: () async { + Navigator.of(context).pop(); + }, + buttonColor: + MzansiInnovationHub.of(context)!.theme.errorColor(), + child: Text( + "Cancel", + style: TextStyle( + color: + MzansiInnovationHub.of(context)!.theme.primaryColor(), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + MihButton( + width: 300, + onPressed: () { + addBookmark(widget.business.business_id); + }, + buttonColor: + MzansiInnovationHub.of(context)!.theme.successColor(), + child: Text( + "Bookmark Business", + style: TextStyle( + color: + MzansiInnovationHub.of(context)!.theme.primaryColor(), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_business_info_card.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_business_info_card.dart index 32a2bcf4..6ef8bbdc 100644 --- a/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_business_info_card.dart +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_business_info_card.dart @@ -1,38 +1,26 @@ 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_objects/business_review.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_alert.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; +import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/components/mih_add_bookmark_alert.dart'; +import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/components/mih_delete_bookmark_alert.dart'; import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/components/mih_review_business_window.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_mzansi_directory_services.dart'; +import 'package:redacted/redacted.dart'; import 'package:supertokens_flutter/supertokens.dart'; import 'package:url_launcher/url_launcher.dart'; class MihBusinessCard extends StatefulWidget { final Business business; final String? startUpSearch; - // final String businessid; - // final String businessName; - // final String cellNumber; - // final String email; - // final String gpsLocation; - // final String? website; - // final double rating; final double width; const MihBusinessCard({ super.key, required this.business, required this.startUpSearch, - // required this.businessid, - // required this.businessName, - // required this.cellNumber, - // required this.email, - // required this.gpsLocation, - // required this.rating, - // this.website, required this.width, }); @@ -41,6 +29,16 @@ class MihBusinessCard extends StatefulWidget { } class _MihBusinessCardState extends State { + Future? _businessReviewFuture; + Future? _bookmarkedBusinessFuture; + + RedactedConfiguration getRedactedConfiguration() { + return RedactedConfiguration( + // redactedColor: Colors.pink, + redactedColor: MzansiInnovationHub.of(context)!.theme.primaryColor(), + ); + } + Future _makePhoneCall(String phoneNumber) async { final Uri url = Uri(scheme: 'tel', path: phoneNumber); if (await canLaunchUrl(url)) { @@ -269,6 +267,7 @@ class _MihBusinessCardState extends State { String subLabel, IconData icon, Color? iconColor, + bool redacted, Function()? ontap, ) { return Material( @@ -288,16 +287,25 @@ class _MihBusinessCardState extends State { child: Row( children: [ Container( + width: 45, + height: 45, decoration: BoxDecoration( color: iconColor, borderRadius: BorderRadius.circular(15), ), padding: const EdgeInsets.all(5.0), - child: Icon( - icon, - size: 35, - color: MzansiInnovationHub.of(context)!.theme.primaryColor(), + child: FittedBox( + child: Icon( + icon, + // size: 35, + color: + MzansiInnovationHub.of(context)!.theme.primaryColor(), + ), ), + ).redacted( + context: context, + redact: redacted, + configuration: getRedactedConfiguration(), ), SizedBox(width: 20), Expanded( @@ -316,6 +324,10 @@ class _MihBusinessCardState extends State { .primaryColor(), height: 1.0, ), + ).redacted( + context: context, + redact: redacted, + configuration: getRedactedConfiguration(), ), Text( subLabel, @@ -326,6 +338,10 @@ class _MihBusinessCardState extends State { .theme .primaryColor(), ), + ).redacted( + context: context, + redact: redacted, + configuration: getRedactedConfiguration(), ), ], ), @@ -345,6 +361,27 @@ class _MihBusinessCardState extends State { ); } + Future getUserBookmark() async { + String user_id = await SuperTokens.getUserId(); + return await MihMzansiDirectoryServices().getUserBookmarkOfBusiness( + user_id, + widget.business.business_id, + ); + } + + bool isValidGps(String coordinateString) { + final RegExp gpsRegex = RegExp( + r"^-?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*-?(1[0-7]\d(\.\d+)?|180(\.0+)?|\d{1,2}(\.\d+)?)$"); + return gpsRegex.hasMatch(coordinateString); + } + + @override + void initState() { + super.initState(); + _businessReviewFuture = getUserReview(); + _bookmarkedBusinessFuture = getUserBookmark(); + } + @override Widget build(BuildContext context) { // double screenWidth = MediaQuery.of(context).size.width; @@ -369,6 +406,7 @@ class _MihBusinessCardState extends State { "Give us a quick call.", Icons.phone, MihColors.getGreenColor(context), + false, () { // print("Calling ${widget.cellNumber}"); _makePhoneCall(widget.business.contact_no); @@ -382,6 +420,7 @@ class _MihBusinessCardState extends State { "Send us an email.", Icons.email, MihColors.getPinkColor(context), + false, () { // print("Emailing ${widget.email}"); _launchEmail( @@ -391,58 +430,182 @@ class _MihBusinessCardState extends State { ); }, ), - Divider( - color: MzansiInnovationHub.of(context)!.theme.primaryColor(), + Visibility( + visible: isValidGps(widget.business.gps_location), + child: Column( + children: [ + Divider( + color: + MzansiInnovationHub.of(context)!.theme.primaryColor(), + ), + _buildContactInfo( + "Location", + "Come visit us.", + Icons.location_on, + MihColors.getOrangeColor(context), + false, + () { + final latitude = double.parse( + widget.business.gps_location.split(',')[0]); + final longitude = double.parse( + widget.business.gps_location.split(',')[1]); + _launchGoogleMapsWithUrl( + latitude: latitude, + longitude: longitude, + ); + }, + ), + ], + ), ), - _buildContactInfo( - "Location", - "Come visit us.", - Icons.location_on, - MihColors.getOrangeColor(context), - () { - final latitude = - double.parse(widget.business.gps_location.split(',')[0]); - final longitude = - double.parse(widget.business.gps_location.split(',')[1]); - _launchGoogleMapsWithUrl( - latitude: latitude, - longitude: longitude, - ); + Visibility( + visible: widget.business.website.isNotEmpty && + widget.business.website != "", + child: Column( + children: [ + Divider( + color: + MzansiInnovationHub.of(context)!.theme.primaryColor(), + ), + _buildContactInfo( + "Website", + "Find out more about us.", + Icons.vpn_lock, + MihColors.getRedColor(context), + false, + () { + _launchWebsite(widget.business.website); + }, + ), + ], + ), + ), + FutureBuilder( + future: _businessReviewFuture, + builder: (context, asyncSnapshot) { + if (asyncSnapshot.connectionState == ConnectionState.waiting) { + // return const SizedBox.shrink(); + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Divider( + color: MzansiInnovationHub.of(context)! + .theme + .primaryColor(), + ), + ), + Container( + child: _buildContactInfo( + "Loading Rating", + "Loading your rating.", + Icons.star_rate_rounded, + MihColors.getYellowColor(context), + true, + null, + ), + ).redacted(context: context, redact: true), + ], + ); + } else { + BusinessReview? businessReview = asyncSnapshot.data; + String ratingDisplayTitle = ""; + if (businessReview == null) { + ratingDisplayTitle = "Rate Us"; + } else { + ratingDisplayTitle = "Update Rating"; + } + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Divider( + color: MzansiInnovationHub.of(context)! + .theme + .primaryColor(), + ), + ), + _buildContactInfo( + ratingDisplayTitle, + "Let us know how we are doing.", + Icons.star_rate_rounded, + MihColors.getYellowColor(context), + false, + () { + businessReviewRatingWindow( + businessReview, true, widget.width); + }, + ), + ], + ); + } }, ), - Visibility( - visible: widget.business.website.isNotEmpty && - widget.business.website != "", - child: Divider( - color: MzansiInnovationHub.of(context)!.theme.primaryColor(), - ), - ), - Visibility( - visible: widget.business.website.isNotEmpty && - widget.business.website != "", - child: _buildContactInfo( - "Website", - "Find out more about us.", - Icons.vpn_lock, - MihColors.getRedColor(context), - () { - _launchWebsite(widget.business.website); - }, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: Divider( - color: MzansiInnovationHub.of(context)!.theme.primaryColor(), - ), - ), - _buildContactInfo( - "Rate Us", - "Let us know how we are doing.", - Icons.star_rate_rounded, - MihColors.getYellowColor(context), - () { - businessReviewRatingWindow(true, widget.width); + FutureBuilder( + future: _bookmarkedBusinessFuture, + builder: (context, asyncSnapshot) { + if (asyncSnapshot.connectionState == ConnectionState.waiting) { + // return const SizedBox.shrink(); + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Divider( + color: MzansiInnovationHub.of(context)! + .theme + .primaryColor(), + ), + ), + Container( + child: _buildContactInfo( + "Loading Bookmark", + "Loading your bookmark.", + Icons.bookmark_add_rounded, + MihColors.getBluishPurpleColor(context), + true, + null, + ), + ), + ], + ); + } else { + BookmarkedBusiness? bookmarkBusiness = asyncSnapshot.data; + String bookmarkDisplayTitle = ""; + if (bookmarkBusiness == null) { + bookmarkDisplayTitle = "Bookmark Us"; + } else { + bookmarkDisplayTitle = "Remove Bookmark"; + } + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Divider( + color: MzansiInnovationHub.of(context)! + .theme + .primaryColor(), + ), + ), + _buildContactInfo( + bookmarkDisplayTitle, + "Save us for later.", + bookmarkBusiness == null + ? Icons.bookmark_add_rounded + : Icons.bookmark_remove_rounded, + MihColors.getBluishPurpleColor(context), + false, + () { + // _launchWebsite(widget.website); + if (bookmarkBusiness == null) { + showAddBookmarkAlert(); + } else { + showDeleteBookmarkAlert(bookmarkBusiness); + } + }, + ), + ], + ); + } }, ), // Padding( @@ -455,7 +618,7 @@ class _MihBusinessCardState extends State { // "Bookmark", // "Save us for later.", // Icons.bookmark_add_rounded, - // MihColors.getBluishPurpleColor(context), + // MihColors.getBluishPurpleColor(context), // () { // // _launchWebsite(widget.website); // print("Saving ${widget.business.Name} to Directory"); @@ -476,115 +639,34 @@ class _MihBusinessCardState extends State { } Future businessReviewRatingWindow( - bool previouslyRated, double width) async { + BusinessReview? myReview, bool previouslyRated, double width) async { showDialog( context: context, - builder: (context) => FutureBuilder( - future: getUserReview(), - builder: (context, asyncSnapshot) { - if (asyncSnapshot.connectionState == ConnectionState.waiting) { - return const Mihloadingcircle( - message: "Checking for previous reviews...", - ); - } else if (asyncSnapshot.connectionState == ConnectionState.done) { - return MihReviewBusinessWindow( - business: widget.business, - businessReview: asyncSnapshot.data, - screenWidth: width, - readOnly: false, - startUpSearch: widget.startUpSearch, - ); - } else { - return MihPackageAlert( - alertColour: MzansiInnovationHub.of(context)!.theme.errorColor(), - alertIcon: Icon( - Icons.warning_rounded, - size: 100, - color: MzansiInnovationHub.of(context)!.theme.errorColor(), - ), - alertTitle: "Error Pulling Data", - alertBody: Column( - children: [ - Text( - "Please ensure you are connectede top the internet and you are running the latest version of MIH then try again.", - style: TextStyle( - color: MzansiInnovationHub.of(context)! - .theme - .secondaryColor(), - fontSize: 15, - ), - ), - ], - ), - ); - } - }, + builder: (context) => MihReviewBusinessWindow( + business: widget.business, + businessReview: myReview, + screenWidth: width, + readOnly: false, ), ); } - void showBookmarkAlert() { + void showAddBookmarkAlert() { showDialog( context: context, - builder: (context) => MihPackageAlert( - alertColour: MihColors.getSecondaryColor(context), - alertIcon: Icon( - Icons.warning_rounded, - size: 100, - color: MihColors.getSecondaryColor(context), - ), - alertTitle: "Bookmark Business", - alertBody: Column( - children: [ - Text( - "Are you sure you want to save ${widget.business.Name} to your Mzansi Directory?", - style: TextStyle( - color: MzansiInnovationHub.of(context)!.theme.secondaryColor(), - fontSize: 15, - ), - ), - const SizedBox(height: 25), - Wrap( - spacing: 10, - runSpacing: 10, - children: [ - MihButton( - width: 300, - onPressed: () async { - Navigator.of(context).pop(); - }, - buttonColor: - MzansiInnovationHub.of(context)!.theme.errorColor(), - child: Text( - "Cancel", - style: TextStyle( - color: - MzansiInnovationHub.of(context)!.theme.primaryColor(), - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - MihButton( - width: 300, - onPressed: () {}, - buttonColor: - MzansiInnovationHub.of(context)!.theme.successColor(), - child: Text( - "Save Business", - style: TextStyle( - color: - MzansiInnovationHub.of(context)!.theme.primaryColor(), - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ], - ), + builder: (context) => MihAddBookmarkAlert( + business: widget.business, ), ); } + + void showDeleteBookmarkAlert(BookmarkedBusiness? bookmarkBusiness) { + showDialog( + context: context, + builder: (context) => MihDeleteBookmarkAlert( + business: widget.business, + bookmarkBusiness: bookmarkBusiness, + startUpSearch: widget.startUpSearch, + )); + } } diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_delete_bookmark_alert.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_delete_bookmark_alert.dart new file mode 100644 index 00000000..ab0e46df --- /dev/null +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_delete_bookmark_alert.dart @@ -0,0 +1,143 @@ +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_button.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_alert.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; +import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_alert_services.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_mzansi_directory_services.dart'; + +class MihDeleteBookmarkAlert extends StatefulWidget { + final Business business; + final BookmarkedBusiness? bookmarkBusiness; + final String? startUpSearch; + const MihDeleteBookmarkAlert({ + super.key, + required this.business, + required this.bookmarkBusiness, + required this.startUpSearch, + }); + + @override + State createState() => _MihDeleteBookmarkAlertState(); +} + +class _MihDeleteBookmarkAlertState extends State { + Future deleteBookmark(int idbookmarked_businesses) async { + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + await MihMzansiDirectoryServices() + .deleteBookmarkedBusiness(idbookmarked_businesses) + .then((statusCode) { + if (statusCode == 200) { + // Navigator.of(context).pop(); //Remove loading circle + // Navigator.of(context).pop(); //Remove window + // Navigator.of(context).pop(); //Remove profile + // Navigator.of(context).pop(); //Remove directory + // Navigator.of(context).pushNamed( + // '/mzansi-directory', + // arguments: MzansiDirectoryArguments( + // startUpSearch: widget.startUpSearch, // startUpSearch + // personalSearch: false, // personalSearch + // ), + // ); + Navigator.of(context).pushNamedAndRemoveUntil( + '/mzansi-directory', + ModalRoute.withName('/'), + arguments: MzansiDirectoryArguments( + personalSearch: false, // personalSearch + packageIndex: 1, + startSearchText: widget.business.Name, + ), + ); + MihAlertServices().successAlert( + "Successfully Removed Bookmark!", + "${widget.business.Name} has successfully been removed your favourite businessess in the Mzansi Directory.", + context, + ); + } else { + //error messagek + Navigator.of(context).pop(); + MihAlertServices().errorAlert( + "Error Adding Bookmark", + "An error occured while add ${widget.business.Name} to you Mzansi Directory, Please try again later.", + context, + ); + } + }); + } + + @override + Widget build(BuildContext context) { + return MihPackageAlert( + alertColour: MihColors.getSecondaryColor(context), + alertIcon: Icon( + Icons.warning_rounded, + size: 100, + color: MihColors.getSecondaryColor(context), + ), + alertTitle: "Remove Bookmark", + alertBody: Column( + children: [ + Text( + "Are you sure you want to remove ${widget.business.Name} from your Mzansi Directory?", + style: TextStyle( + color: MzansiInnovationHub.of(context)!.theme.secondaryColor(), + fontSize: 15, + ), + ), + const SizedBox(height: 25), + Wrap( + spacing: 10, + runSpacing: 10, + children: [ + MihButton( + width: 300, + onPressed: () async { + Navigator.of(context).pop(); + }, + buttonColor: + MzansiInnovationHub.of(context)!.theme.successColor(), + child: Text( + "Cancel", + style: TextStyle( + color: + MzansiInnovationHub.of(context)!.theme.primaryColor(), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + MihButton( + width: 300, + onPressed: () { + // todo: remove bookmark + deleteBookmark( + widget.bookmarkBusiness!.idbookmarked_businesses); + }, + buttonColor: + MzansiInnovationHub.of(context)!.theme.errorColor(), + child: Text( + "Remove Business", + style: TextStyle( + color: + MzansiInnovationHub.of(context)!.theme.primaryColor(), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_review_business_window.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_review_business_window.dart index 6bf06352..ab3b2318 100644 --- a/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_review_business_window.dart +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/components/mih_review_business_window.dart @@ -23,14 +23,12 @@ class MihReviewBusinessWindow extends StatefulWidget { final BusinessReview? businessReview; final double screenWidth; final bool readOnly; - final String? startUpSearch; const MihReviewBusinessWindow({ super.key, required this.business, required this.businessReview, required this.screenWidth, required this.readOnly, - required this.startUpSearch, }); @override @@ -101,8 +99,8 @@ class _MihReviewBusinessWindowState extends State { Navigator.of(context).pushNamed( '/mzansi-directory', arguments: MzansiDirectoryArguments( - widget.startUpSearch, // startUpSearch - false, // personalSearch + personalSearch: false, // personalSearch + startSearchText: widget.business.Name, ), ); MihAlertServices().successAlert( @@ -191,8 +189,8 @@ class _MihReviewBusinessWindowState extends State { Navigator.of(context).pushNamed( '/mzansi-directory', arguments: MzansiDirectoryArguments( - widget.startUpSearch, // startUpSearch - false, // personalSearch + personalSearch: false, // personalSearch + startSearchText: widget.business.Name, ), ); MihAlertServices().successAlert( @@ -227,8 +225,8 @@ class _MihReviewBusinessWindowState extends State { Navigator.of(context).pushNamed( '/mzansi-directory', arguments: MzansiDirectoryArguments( - widget.startUpSearch, // startUpSearch - false, // personalSearch + personalSearch: false, // personalSearch + startSearchText: widget.business.Name, ), ); MihAlertServices().successAlert( @@ -386,7 +384,8 @@ class _MihReviewBusinessWindowState extends State { emptyColor: MzansiInnovationHub.of(context)! .theme .secondaryColor(), - halfFilledColor: MihColors.getYellowColor(context), isHalfAllowed: true, + halfFilledColor: MihColors.getYellowColor(context), + isHalfAllowed: true, initialRating: widget.businessReview != null ? double.parse(_reviewScoreController.text) : 1, diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/mzansi_business_profile.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/mzansi_business_profile.dart index 11d9caea..7987eedf 100644 --- a/Frontend/lib/mih_packages/mzansi_profile/business_profile/mzansi_business_profile.dart +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/mzansi_business_profile.dart @@ -1,3 +1,4 @@ +import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_reviews.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_file_services.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_action.dart'; @@ -98,6 +99,11 @@ class _MzansiBusinessProfileState extends State { _selcetedIndex = 3; }); }; + temp[const Icon(Icons.star_rate_rounded)] = () { + setState(() { + _selcetedIndex = 4; + }); + }; return MihPackageTools( tools: temp, selcetedIndex: _selcetedIndex, @@ -149,6 +155,7 @@ class _MzansiBusinessProfileState extends State { // MihBusinessProfile(arguments: widget.arguments), MihMyBusinessTeam(arguments: widget.arguments), MihBusinessUserSearch(arguments: widget.arguments), + MihBusinessReviews(business: widget.arguments.business!), ]; return toolBodies; } @@ -159,6 +166,7 @@ class _MzansiBusinessProfileState extends State { "User", "Team", "Add Member", + "Reviews", ]; return toolTitles; } diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_reviews.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_reviews.dart index 86035ec8..12683c97 100644 --- a/Frontend/lib/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_reviews.dart +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_reviews.dart @@ -40,7 +40,6 @@ class _MihBusinessReviewsState extends State { businessReview: businessReview, screenWidth: width, readOnly: true, - startUpSearch: null, ); }, ); @@ -59,6 +58,7 @@ class _MihBusinessReviewsState extends State { } else if (asyncSnapshot.connectionState == ConnectionState.done && asyncSnapshot.hasData) { List reviews = asyncSnapshot.data!; + print("Reviews: ${reviews.length}"); if (reviews.isEmpty) { return Column( children: [ @@ -133,11 +133,16 @@ class _MihBusinessReviewsState extends State { fontWeight: FontWeight.bold, ), ), - Text( - "${reviews[index].rating_description.substring(0, reviews[index].rating_description.length >= descriptionDisplayCOunt ? descriptionDisplayCOunt : reviews[index].rating_description.length - 1)}${reviews[index].rating_description.length >= descriptionDisplayCOunt ? "..." : ""}", - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.normal, + Visibility( + visible: reviews[index].rating_description.isNotEmpty, + child: Text( + reviews[index].rating_description.isEmpty + ? "" + : "${reviews[index].rating_description.substring(0, reviews[index].rating_description.length >= descriptionDisplayCOunt ? descriptionDisplayCOunt : reviews[index].rating_description.length - 1)}${reviews[index].rating_description.length >= descriptionDisplayCOunt ? "..." : ""}", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.normal, + ), ), ), Text( diff --git a/Frontend/lib/mih_packages/mzansi_wallet/components/mih_card_display.dart b/Frontend/lib/mih_packages/mzansi_wallet/components/mih_card_display.dart index e20ebe70..1f086c9a 100644 --- a/Frontend/lib/mih_packages/mzansi_wallet/components/mih_card_display.dart +++ b/Frontend/lib/mih_packages/mzansi_wallet/components/mih_card_display.dart @@ -150,6 +150,9 @@ class _MihCardDisplayState extends State { case "fresmart": return Image.asset( 'lib/mih_components/mih_package_components/assets/images/loyalty_cards/mini/fresmart-min.png'); + case "total energies": + return Image.asset( + 'lib/mih_components/mih_package_components/assets/images/loyalty_cards/mini/total_energies-min.png'); default: return null; } diff --git a/Frontend/lib/mih_packages/mzansi_wallet/package_tools/mih_cards.dart b/Frontend/lib/mih_packages/mzansi_wallet/package_tools/mih_cards.dart index cb8fbdcf..d2c03791 100644 --- a/Frontend/lib/mih_packages/mzansi_wallet/package_tools/mih_cards.dart +++ b/Frontend/lib/mih_packages/mzansi_wallet/package_tools/mih_cards.dart @@ -161,6 +161,7 @@ class _MihCardsState extends State { "Spar", "Spur", "TFG Group", + "Total Energies", "Toys R Us", "Woermann Brock", "Woolworths" @@ -367,7 +368,7 @@ class _MihCardsState extends State { builder: (BuildContext context, List value, Widget? child) { return BuildLoyaltyCardList( - cardList: searchShopName.value, + cardList: value, signedInUser: widget.signedInUser, navIndex: 0, bannerAd: _bannerAd, diff --git a/Frontend/lib/mih_services/mih_business_details_services.dart b/Frontend/lib/mih_services/mih_business_details_services.dart index 7e5f0cf1..252c707d 100644 --- a/Frontend/lib/mih_services/mih_business_details_services.dart +++ b/Frontend/lib/mih_services/mih_business_details_services.dart @@ -9,12 +9,42 @@ import '../mih_components/mih_pop_up_messages/mih_error_message.dart'; import 'package:supertokens_flutter/http.dart' as http; class MihBusinessDetailsServices { + Future> fetchAllBusinessTypes() async { + var response = await http.get( + Uri.parse("${AppEnviroment.baseApiUrl}/business/types/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + ); + if (response.statusCode == 200) { + List jsonList = jsonDecode(response.body); + List businessTypes = + jsonList.map((item) => item['type'].toString()).toList(); + return businessTypes; + } else { + return []; + } + } + Future> searchBusinesses( String searchText, + String searchType, BuildContext context, ) async { + String newSearchText = "All"; + if (searchText.isNotEmpty) { + newSearchText = searchText; + } + String newSearchType = "All"; + if (searchType.isNotEmpty) { + newSearchType = searchType; + } + if (searchText.isEmpty && searchType.isEmpty) { + return []; + } var response = await http.get( - Uri.parse("${AppEnviroment.baseApiUrl}/businesses/search/$searchText"), + Uri.parse( + "${AppEnviroment.baseApiUrl}/business/search/$newSearchType/$newSearchText"), headers: { "Content-Type": "application/json; charset=UTF-8" }, @@ -29,7 +59,7 @@ class MihBusinessDetailsServices { } } - Future getBusinessDetails( + Future getBusinessDetailsByUser( String app_id, ) async { var response = await http.get( @@ -47,6 +77,25 @@ class MihBusinessDetailsServices { } } + Future getBusinessDetailsByBusinessId( + String business_id, + ) async { + var response = await http.get( + Uri.parse( + "${AppEnviroment.baseApiUrl}/business/business_id/$business_id"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + ); + if (response.statusCode == 200) { + String body = response.body; + var jsonBody = jsonDecode(body); + return Business.fromJson(jsonBody); + } else { + return null; + } + } + Future createBusinessDetails( String appId, String busineName, diff --git a/Frontend/lib/mih_services/mih_mzansi_directory_services.dart b/Frontend/lib/mih_services/mih_mzansi_directory_services.dart index 710c2c3e..886b4cce 100644 --- a/Frontend/lib/mih_services/mih_mzansi_directory_services.dart +++ b/Frontend/lib/mih_services/mih_mzansi_directory_services.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:mzansi_innovation_hub/mih_components/mih_objects/bookmarked_business.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/business_review.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_env.dart'; import 'package:supertokens_flutter/http.dart' as http; @@ -6,6 +7,10 @@ import 'package:supertokens_flutter/http.dart' as http; class MihMzansiDirectoryServices { final baseAPI = AppEnviroment.baseApiUrl; +//######################################################## +//# Business Ratings # +//######################################################## + Future getUserReviewOfBusiness( String app_id, String business_id, @@ -33,10 +38,9 @@ class MihMzansiDirectoryServices { List businessReviews = List.from( l.map((model) => BusinessReview.fromJson(model))); return businessReviews; - } else if (response.statusCode == 404){ + } else if (response.statusCode == 404) { return []; - } - else { + } else { throw Exception('failed to fetch Business Reviews'); } } @@ -128,4 +132,84 @@ class MihMzansiDirectoryServices { return response.statusCode; } } + +//######################################################## +//# Bookmarked Business # +//######################################################## + + Future getUserBookmarkOfBusiness( + String app_id, + String business_id, + ) async { + final response = await http.get(Uri.parse( + "${AppEnviroment.baseApiUrl}/mzansi-directory/bookmarked-business/$app_id/$business_id")); + // print(response.statusCode); + if (response.statusCode == 200) { + String body = response.body; + var jsonBody = jsonDecode(body); + BookmarkedBusiness? BookmarkedBus = BookmarkedBusiness.fromJson(jsonBody); + return BookmarkedBus; + } else { + return null; + } + } + + Future> getAllUserBookmarkedBusiness( + String app_id, + ) async { + final response = await http.get(Uri.parse( + "${AppEnviroment.baseApiUrl}/mzansi-directory/bookmarked-business/user/all/$app_id")); + if (response.statusCode == 200) { + Iterable l = jsonDecode(response.body); + List businessReviews = List.from( + l.map((model) => BookmarkedBusiness.fromJson(model))); + return businessReviews; + } else if (response.statusCode == 404) { + return []; + } else { + throw Exception('failed to fetch User Bookmarked Business'); + } + } + + Future addBookmarkedBusiness( + String app_id, + String business_id, + ) async { + var response = await http.post( + Uri.parse( + "${AppEnviroment.baseApiUrl}/mzansi-directory/bookmarked-business/insert/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({ + "app_id": app_id, + "business_id": business_id, + }), + ); + if (response.statusCode == 201) { + return response.statusCode; + } else { + return response.statusCode; + } + } + + Future deleteBookmarkedBusiness( + int idbookmarked_businesses, + ) async { + var response = await http.delete( + Uri.parse( + "${AppEnviroment.baseApiUrl}/mzansi-directory/bookmarked-business/delete/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({ + "idbookmarked_businesses": idbookmarked_businesses, + }), + ); + if (response.statusCode == 200) { + return response.statusCode; + } else { + return response.statusCode; + } + } } diff --git a/Frontend/lib/mih_services/mih_service_calls.dart b/Frontend/lib/mih_services/mih_service_calls.dart index a1b701cc..411ec8c7 100644 --- a/Frontend/lib/mih_services/mih_service_calls.dart +++ b/Frontend/lib/mih_services/mih_service_calls.dart @@ -78,7 +78,8 @@ class MIHApiCalls { } // Get Businessdata - Business? business = await MihBusinessDetailsServices().getBusinessDetails( + Business? business = + await MihBusinessDetailsServices().getBusinessDetailsByUser( uid, ); if (business != null) { diff --git a/backend/Dockerfile b/backend/Dockerfile index 5e940a58..dfd1da3d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /app COPY requirements.txt ./ RUN --mount=type=cache,target=/root/.cache/pip \ - pip install -r requirements.txt + pip --default-timeout=120 install -r requirements.txt # COPY . ./app diff --git a/backend/mih_database/mihDbObjects.py b/backend/mih_database/mihDbObjects.py index 0ce1045f..df8c6e54 100644 --- a/backend/mih_database/mihDbObjects.py +++ b/backend/mih_database/mihDbObjects.py @@ -64,10 +64,21 @@ class BusinessRating(Base): rating_description = Column(String(256), nullable=False, server_default="") rating_score = Column(String(45), nullable=False, server_default="") date_time = Column(DateTime, nullable=True) - def __repr__(self): return ( f"" + ) +class BookmarkedBusiness(Base): + __tablename__ = 'bookmarked_businesses' + __table_args__ = {'schema': 'mzansi_directory'} + idbookmarked_businesses = Column(Integer, primary_key=True) + app_id = Column(String(128), nullable=False, server_default="") + business_id = Column(String(128), nullable=False, server_default="") + created_date = Column(DateTime, nullable=True) + def __repr__(self): + return ( + f"" ) \ No newline at end of file diff --git a/backend/routers/business.py b/backend/routers/business.py index a3a91826..797fb4b4 100644 --- a/backend/routers/business.py +++ b/backend/routers/business.py @@ -1,7 +1,11 @@ -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, HTTPException, status from pydantic import BaseModel #from ..mih_database import dbConnection import mih_database +import mih_database.mihDbConnections +from mih_database.mihDbObjects import User, Business, BusinessRating, BookmarkedBusiness +from sqlalchemy import desc, or_ +from sqlalchemy.orm import Session #SuperToken Auth from front end from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.session import SessionContainer @@ -60,90 +64,162 @@ class businessUpdateRequestV2(BaseModel): # Get List of all files -@router.get("/businesses/search/{search}", tags=["MIH Business"]) -async def read_all_businesses(search: str, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) - db = mih_database.dbConnection.dbAppDataConnect() - cursor = db.cursor() - query = "SELECT business.business_id, business.Name, business.type, business.registration_no, " - query += "business.logo_name, business.logo_path, business.contact_no, business.bus_email, " - query += "business.gps_location, " - query += "practice_no, vat_no, " - query += "website, rating, mission_vision " - query += "FROM business " - query += "WHERE LOWER(business.Name) LIKE %s OR LOWER(business.type) LIKE %s " - query += "OR LOWER(business.bus_email) LIKE %s OR LOWER(business.mission_vision) LIKE %s" - search_term = f"%{search.lower()}%" # Add wildcards and lowercase - cursor.execute(query, (search_term, search_term, search_term, search_term)) - items = [ - { - "business_id": item[0], - "Name": item[1], - "type": item[2], - "registration_no": item[3], - "logo_name": item[4], - "logo_path": item[5], - "contact_no": item[6], - "bus_email": item[7], - "app_id": "", - "gps_location": item[8], - "practice_no": item[9], - "vat_no": item[10], - "website": item[11], - "rating": item[12], - "mission_vision": item[13], - } - for item in cursor.fetchall() - ] - cursor.close() - db.close() - return items +@router.get("/business/types/", tags=["MIH Business"]) +async def read_business_by_business_id(session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + queryResults = dbSession.query(Business.type).distinct().order_by(Business.type).all() + response_data = [{"type": t[0]} for t in queryResults] + return response_data + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to retrieve records due to an internal server error." + ) + finally: + dbSession.close() + +# Get List of all files +@router.get("/business/search/{type}/{search}", tags=["MIH Business"]) +async def read_all_businesses(search: str, type: str, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + queryResults = dbSession.query(Business) + type_term_with_wildcards = "" + if type != "All": + type_term_with_wildcards = f"%{type.lower()}%" + queryResults = queryResults.filter( + Business.type.ilike(type_term_with_wildcards) + ) + search_term_with_wildcards = "" + if search != "All": + search_term_with_wildcards = f"%{search.lower()}%" + queryResults = queryResults.filter( + or_( + Business.Name.ilike(search_term_with_wildcards), + Business.bus_email.ilike(search_term_with_wildcards), + Business.mission_vision.ilike(search_term_with_wildcards), + ) + ) + queryResults = queryResults.all() + response_data = [] + for business in queryResults: + response_data.append({ + "business_id": business.business_id, + "Name": business.Name, + "type": business.type, + "registration_no": business.registration_no, + "logo_name": business.logo_name, + "logo_path": business.logo_path, + "contact_no": business.contact_no, + "bus_email": business.bus_email, + "app_id": "", + "gps_location": business.gps_location, + "practice_no": business.practice_no, + "vat_no": business.vat_no, + "website": business.website, + "rating": business.rating, + "mission_vision": business.mission_vision, + }) + + return response_data + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to retrieve records due to an internal server error." + ) + finally: + dbSession.close() + # db = mih_database.dbConnection.dbAppDataConnect() + # cursor = db.cursor() + # query = "SELECT business.business_id, business.Name, business.type, business.registration_no, " + # query += "business.logo_name, business.logo_path, business.contact_no, business.bus_email, " + # query += "business.gps_location, " + # query += "practice_no, vat_no, " + # query += "website, rating, mission_vision " + # query += "FROM business " + # query += "WHERE LOWER(business.Name) LIKE %s OR LOWER(business.type) LIKE %s " + # query += "OR LOWER(business.bus_email) LIKE %s OR LOWER(business.mission_vision) LIKE %s" + # search_term = f"%{search.lower()}%" # Add wildcards and lowercase + # cursor.execute(query, (search_term, search_term, search_term, search_term)) + # items = [ + # { + # "business_id": item[0], + # "Name": item[1], + # "type": item[2], + # "registration_no": item[3], + # "logo_name": item[4], + # "logo_path": item[5], + # "contact_no": item[6], + # "bus_email": item[7], + # "app_id": "", + # "gps_location": item[8], + # "practice_no": item[9], + # "vat_no": item[10], + # "website": item[11], + # "rating": item[12], + # "mission_vision": item[13], + # } + # for item in cursor.fetchall() + # ] + # cursor.close() + # db.close() + # return items # Get List of all files @router.get("/business/business_id/{business_id}", tags=["MIH Business"]) async def read_business_by_business_id(business_id: str, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) - db = mih_database.dbConnection.dbAppDataConnect() - cursor = db.cursor() - query = "SELECT business.business_id, business.Name, business.type, business.registration_no, " - query += "business.logo_name, business.logo_path, business.contact_no, business.bus_email, " - query += "business_users.app_id, business.gps_location, " - query += "practice_no, vat_no, " - query += "website, rating, mission_vision " - query += "FROM business " - query += "inner join business_users " - query += "on business.business_id=business_users.business_id " - query += "where business.business_id = %s" + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) try: - cursor.execute(query, (business_id,)) - except Exception as error: - raise HTTPException(status_code=404, detail="Failed to pull records") - items = [ - { - "business_id": item[0], - "Name": item[1], - "type": item[2], - "registration_no": item[3], - "logo_name": item[4], - "logo_path": item[5], - "contact_no": item[6], - "bus_email": item[7], - "app_id": item[8], - "gps_location": item[9], - "practice_no": item[10], - "vat_no": item[11], - "website": item[12], - "rating": item[13], - "mission_vision": item[14], - } - for item in cursor.fetchall() - ] - # - cursor.close() - db.close() - if(len(items)!= 0): - return items[0] - else: - raise HTTPException(status_code=404, detail="No record found") - + queryResults = dbSession.query(Business).\ + filter( + Business.business_id == business_id, + ).first() + if queryResults: + return { + "business_id": queryResults.business_id, + "Name": queryResults.Name, + "type": queryResults.type, + "registration_no": queryResults.registration_no, + "logo_name": queryResults.logo_name, + "logo_path": queryResults.logo_path, + "contact_no": queryResults.contact_no, + "bus_email": queryResults.bus_email, + "app_id": "", + "gps_location": queryResults.gps_location, + "practice_no": queryResults.practice_no, + "vat_no": queryResults.vat_no, + "website": queryResults.website, + "rating": queryResults.rating, + "mission_vision": queryResults.mission_vision, + } + else: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Business not found for the given business_id." + ) + except HTTPException as http_exc: + # Re-raise HTTPException directly if it was raised within the try block + raise http_exc + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to retrieve records due to an internal server error." + ) + finally: + dbSession.close() # Get List of all files @router.get("/business/app_id/{app_id}", tags=["MIH Business"]) diff --git a/backend/routers/mzansi_directory.py b/backend/routers/mzansi_directory.py index 3ecc28ec..6da5ffc2 100644 --- a/backend/routers/mzansi_directory.py +++ b/backend/routers/mzansi_directory.py @@ -4,7 +4,7 @@ from sqlalchemy.orm import Session from pydantic import BaseModel from datetime import datetime import mih_database.mihDbConnections -from mih_database.mihDbObjects import User, Business, BusinessRating +from mih_database.mihDbObjects import User, Business, BusinessRating, BookmarkedBusiness from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.session import SessionContainer from fastapi import Depends @@ -40,6 +40,17 @@ class BusinessRatingUpdateRequest(BaseModel): rating_old_score: str current_rating: str +class BookmarkedBusinessInsertRequest(BaseModel): + app_id: str + business_id: str + +class BookmarkedBusinessDeleteRequest(BaseModel): + idbookmarked_businesses: int + +######################################################## +# Business Ratings # +######################################################## + @router.get("/mzansi-directory/business-ratings/user/{app_id}/{business_id}", tags=["Mzansi Directory"]) async def read_all_ratings_by_business_id(app_id: str,business_id: str, session: SessionContainer = Depends(verify_session())): # , session: SessionContainer = Depends(verify_session()) dbEngine = mih_database.mihDbConnections.dbAllConnect() @@ -295,4 +306,154 @@ async def UpdatePatient(itemRequest : BusinessRatingUpdateRequest, session: Sess ) finally: dbSession.close() - return {"message": "Successfully wUpdated Record"} \ No newline at end of file + return {"message": "Successfully wUpdated Record"} + +######################################################## +# Bookmarked Business # +######################################################## + +@router.get("/mzansi-directory/bookmarked-business/{app_id}/{business_id}", tags=["Mzansi Directory"]) +async def read_all_ratings_by_business_id(app_id: str,business_id: str, session: SessionContainer = Depends(verify_session())): # , session: SessionContainer = Depends(verify_session()) + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + queryResults = dbSession.query(BookmarkedBusiness, Business).\ + join(Business, BookmarkedBusiness.business_id == Business.business_id).\ + filter( + BookmarkedBusiness.business_id == business_id, + BookmarkedBusiness.app_id == app_id, + ).order_by( + desc(BookmarkedBusiness.created_date) + ).first() + if queryResults: + bookmark_obj, bus_obj = queryResults + return { + "idbookmarked_businesses": bookmark_obj.idbookmarked_businesses, + "app_id": bookmark_obj.app_id, + "business_id": bookmark_obj.business_id, + "business_name": bus_obj.Name, + "created_date": bookmark_obj.created_date, + } + else: + # Return an empty response or a specific message + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Bookmarked Business rating not found for the given app_id & business_id." + ) + except HTTPException as http_exc: + # Re-raise HTTPException directly if it was raised within the try block + raise http_exc + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to retrieve records due to an internal server error." + ) + finally: + dbSession.close() + +@router.get("/mzansi-directory/bookmarked-business/user/all/{app_id}/", tags=["Mzansi Directory"]) +async def read_all_ratings_by_business_id(app_id: str, session: SessionContainer = Depends(verify_session())): # , session: SessionContainer = Depends(verify_session()) + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + queryResults = dbSession.query(BookmarkedBusiness, Business).\ + join(Business, BookmarkedBusiness.business_id == Business.business_id).\ + filter( + BookmarkedBusiness.app_id == app_id, + ).order_by( + desc(BookmarkedBusiness.created_date) + ).all() + response_data = [] + for rating_obj, bus_obj in queryResults: + response_data.append({ + "idbookmarked_businesses": rating_obj.idbookmarked_businesses, + "app_id": rating_obj.app_id, + "business_id": rating_obj.business_id, + "business_name": bus_obj.Name, + "created_date": rating_obj.created_date, + }) + if len(response_data) > 0: + return response_data + else: + # Return an empty response or a specific message + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Bookmarked Business not found for the given app_id." + ) + except HTTPException as http_exc: + # Re-raise HTTPException directly if it was raised within the try block + raise http_exc + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to retrieve records due to an internal server error." + ) + finally: + dbSession.close() + +@router.post("/mzansi-directory/bookmarked-business/insert/", tags=["Mzansi Directory"], status_code=201) +async def insert_loyalty_card(itemRequest : BookmarkedBusinessInsertRequest): #, session: SessionContainer = Depends(verify_session()) + dbEngine = mih_database.mihDbConnections.dbAllConnect() + nowDateTime = datetime.now() + formatedDateTime = nowDateTime.strftime("%Y-%m-%d %H:%M:%S") + dbSession = Session(dbEngine) + try: + # add business rating + new_bookmarked_business = BookmarkedBusiness( + app_id=itemRequest.app_id, + business_id=itemRequest.business_id, + created_date=formatedDateTime + ) + dbSession.add(new_bookmarked_business) + dbSession.flush() # Ensure the new rating is added to the session + dbSession.commit() # Commit the session to save changes + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to insert records due to an internal server error." + ) + finally: + dbSession.close() + return {"message": "Successfully Created Record"} + +@router.delete("/mzansi-directory/bookmarked-business/delete/", tags=["Mzansi Directory"]) +async def Delete_loyalty_card(itemRequest : BookmarkedBusinessDeleteRequest, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + # delete business rating + rating_to_delete = dbSession.query(BookmarkedBusiness).\ + get( + itemRequest.idbookmarked_businesses + ) + if not rating_to_delete: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Bookmarked Business with ID {itemRequest.idbusiness_ratings} not found." + ) + dbSession.delete(rating_to_delete) + dbSession.flush() # Ensure the new rating is added to the session + dbSession.commit() # Commit the session to save changes + except HTTPException as http_exc: + # Re-raise HTTPException directly if it was raised within the try block + raise http_exc + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to insert records due to an internal server error." + ) + finally: + dbSession.close() + return {"message": "Successfully Deleted Record"}