Merge pull request #240 from yaso-meth/NEW--Bookmark-Business

NEW--Bookmark-Business
This commit is contained in:
yaso-meth
2025-08-01 12:00:09 +02:00
committed by GitHub
74 changed files with 1680 additions and 508 deletions

View File

@@ -71,8 +71,6 @@ class _MzansiInnovationHubState extends State<MzansiInnovationHub> {
double width = MediaQuery.sizeOf(context).width; double width = MediaQuery.sizeOf(context).width;
theme.setScreenType(width); theme.setScreenType(width);
precacheImage(theme.loadingImage(), context); precacheImage(theme.loadingImage(), context);
precacheImage(theme.logoImage(), context);
precacheImage(theme.logoFrame(), context);
return MaterialApp( return MaterialApp(
title: getTitle(), title: getTitle(),
themeMode: ThemeMode.dark, themeMode: ThemeMode.dark,

View File

@@ -229,13 +229,15 @@ class MzansiAiArguments {
} }
class MzansiDirectoryArguments { class MzansiDirectoryArguments {
final String? startUpSearch;
final bool personalSearch; final bool personalSearch;
final int? packageIndex;
final String? startSearchText;
MzansiDirectoryArguments( MzansiDirectoryArguments({
this.startUpSearch, required this.personalSearch,
this.personalSearch, this.packageIndex,
); required this.startSearchText,
});
} }
class TestArguments { class TestArguments {

View File

@@ -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<String, dynamic> 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'),
};
}
}

View File

@@ -40,7 +40,8 @@ class BusinessReview {
date_time: date_time, date_time: date_time,
reviewer: reviewer, reviewer: reviewer,
), ),
_ => throw const FormatException('Failed to load loyalty card objects'), _ =>
throw const FormatException('Failed to load Business Review objects'),
}; };
} }
} }

View File

@@ -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_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_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_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_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_alert_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_location_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_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_time_field.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_toggle.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_toggle.dart';
import 'package:redacted/redacted.dart';
class PackageToolOne extends StatefulWidget { class PackageToolOne extends StatefulWidget {
final AppUser user; final AppUser user;
@@ -291,16 +293,34 @@ class _PackageToolOneState extends State<PackageToolOne> {
} }
}), }),
const SizedBox(height: 10), const SizedBox(height: 10),
// MihBusinessCard( Text("This text should be redacted").redacted(
// businessid: "123456", context: context,
// businessName: "Mzansi Innovation Hub", redact: true,
// cellNumber: "0788300006", ),
// email: "yasien.meth@mzansi-innovation-hub.co.za", MihBusinessCard(
// gpsLocation: "-26.1853611, 28.134664", business: Business(
// website: "business_id",
// "https://app.mzansi-innovation-hub.co.za/privacy.html", "Name",
// rating: 3.25, "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), const SizedBox(height: 10),
Divider( Divider(
color: color:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -44,7 +44,7 @@ class _MihPackageState extends State<MihPackage>
int nextPage = int nextPage =
currentPage + 1 < widget.appBody.length ? currentPage + 1 : currentPage; currentPage + 1 < widget.appBody.length ? currentPage + 1 : currentPage;
if (nextPage != currentPage) { if (nextPage != currentPage) {
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 100));
await _pageController.animateTo( await _pageController.animateTo(
currentOffset + peakOffset, currentOffset + peakOffset,
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
@@ -86,10 +86,24 @@ class _MihPackageState extends State<MihPackage>
vsync: this, vsync: this,
duration: const Duration(milliseconds: 400), 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) { 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((_) { 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();
}
});
}
}); });
} }
} }

View File

@@ -109,6 +109,7 @@ class _MihSearchBarState extends State<MihSearchBar> {
color: widget.fillColor, color: widget.fillColor,
child: AnimatedContainer( child: AnimatedContainer(
// Keep AnimatedContainer for width/height transitions // Keep AnimatedContainer for width/height transitions
alignment: Alignment.centerLeft,
width: widget.width, width: widget.width,
height: widget.height ?? 50, height: widget.height ?? 50,
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
@@ -123,6 +124,7 @@ class _MihSearchBarState extends State<MihSearchBar> {
), ),
), ),
child: TextField( child: TextField(
textAlignVertical: TextAlignVertical.center,
controller: widget.controller, // Assign the controller controller: widget.controller, // Assign the controller
focusNode: widget.searchFocusNode, focusNode: widget.searchFocusNode,
autocorrect: true, autocorrect: true,
@@ -134,17 +136,20 @@ class _MihSearchBarState extends State<MihSearchBar> {
style: TextStyle( style: TextStyle(
color: widget.hintColor, color: widget.hintColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 16,
), ),
cursorColor: widget.hintColor, cursorColor: widget.hintColor,
decoration: InputDecoration( decoration: InputDecoration(
isDense: true,
hintText: widget.hintText, hintText: widget.hintText,
hintStyle: TextStyle( hintStyle: TextStyle(
color: widget.hintColor, color: widget.hintColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w600,
fontSize: 16,
), ),
border: InputBorder.none, border: InputBorder.none,
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 20.0, vertical: 12.0), const EdgeInsets.symmetric(horizontal: 10.0, vertical: 15.0),
prefixIcon: GestureDetector( prefixIcon: GestureDetector(
onTap: widget.onPrefixIconTap, onTap: widget.onPrefixIconTap,
child: getPrefixIcon(), child: getPrefixIcon(),
@@ -152,12 +157,15 @@ class _MihSearchBarState extends State<MihSearchBar> {
suffixIcon: Row( suffixIcon: Row(
// Use a Row for multiple suffix icons // Use a Row for multiple suffix icons
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
// Optional suffix tools // Optional suffix tools
if (widget.suffixTools != null) ...widget.suffixTools!, if (widget.suffixTools != null) ...widget.suffixTools!,
// Clear Icon (conditionally visible) // Clear Icon (conditionally visible)
if (_showClearIcon) // Only show if input is not empty if (_showClearIcon) // Only show if input is not empty
IconButton( IconButton(
iconSize: 35,
icon: Icon(Icons.clear, icon: Icon(Icons.clear,
color: widget.hintColor), // Clear icon color: widget.hintColor), // Clear icon
onPressed: widget.onClearIconTap ?? onPressed: widget.onClearIconTap ??

View File

@@ -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() { AssetImage loadingImage() {
if (mode == "Dark") { if (mode == "Dark") {
loading = const AssetImage( loading = const AssetImage(

View File

@@ -47,8 +47,6 @@ class _MihInfoState extends State<MihInfo> {
bio += "(University of the Western Cape)\n"; bio += "(University of the Western Cape)\n";
bio += bio +=
"6 Year of banking experience with one of the big 5 banks of South Africa."; "6 Year of banking experience with one of the big 5 banks of South Africa.";
ImageProvider logoFrame =
MzansiInnovationHub.of(context)!.theme.altLogoFrame();
return Wrap( return Wrap(
alignment: WrapAlignment.center, alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center, crossAxisAlignment: WrapCrossAlignment.center,
@@ -61,18 +59,22 @@ class _MihInfoState extends State<MihInfo> {
alignment: Alignment.center, alignment: Alignment.center,
fit: StackFit.loose, fit: StackFit.loose,
children: [ children: [
CircleAvatar( Padding(
backgroundColor: padding: const EdgeInsets.only(left: 4.0),
MzansiInnovationHub.of(context)!.theme.primaryColor(), child: CircleAvatar(
backgroundImage: const AssetImage( backgroundColor:
"lib/mih_components/mih_package_components/assets/images/founder.jpg"), MzansiInnovationHub.of(context)!.theme.primaryColor(),
//'https://media.licdn.com/dms/image/D4D03AQGd1-QhjtWWpA/profile-displayphoto-shrink_400_400/0/1671698053061?e=2147483647&v=beta&t=a3dJI5yxs5-KeXjj10LcNCFuC9IOfa8nNn3k_Qyr0CA'), backgroundImage: const AssetImage(
radius: 75, "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),
)
], ],
), ),
), ),

View File

@@ -61,35 +61,6 @@ class _MIHAppDrawerState extends State<MIHAppDrawer> {
backgroundColor: backgroundColor:
MzansiInnovationHub.of(context)!.theme.secondaryColor(), 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),
// )
// ],
// ),
); );
} }

View File

@@ -86,24 +86,22 @@ class _MIHHomeLegacyState extends State<MIHHomeLegacy> {
// ); // );
void setAppsNewPersonal(List<MIHTile> tileList) { void setAppsNewPersonal(List<MIHTile> tileList) {
ImageProvider logo = MzansiInnovationHub.of(context)!.theme.logoImage();
if (widget.signedInUser.fname == "") { if (widget.signedInUser.fname == "") {
tileList.add(MIHTile( // tileList.add(MIHTile(
videoID: "jFV3NN65DtQ", // videoID: "jFV3NN65DtQ",
onTap: () { // onTap: () {
Navigator.of(context).pushNamed('/mzansi-profile', // Navigator.of(context).pushNamed('/mzansi-profile',
arguments: AppProfileUpdateArguments( // arguments: AppProfileUpdateArguments(
widget.signedInUser, widget.propicFile)); // widget.signedInUser, widget.propicFile));
}, // },
tileName: "Setup Profie", // tileName: "Setup Profie",
tileIcon: Padding( // tileIcon: Padding(
padding: const EdgeInsets.all(15.0), // padding: const EdgeInsets.all(15.0),
child: Image(image: logo), // child: Image(image: logo),
), // ),
p: getPrim(), // p: getPrim(),
s: getSec(), // s: getSec(),
)); // ));
} }
} }
@@ -128,27 +126,26 @@ class _MIHHomeLegacyState extends State<MIHHomeLegacy> {
} }
void setAppsPersonal(List<MIHTile> tileList) { void setAppsPersonal(List<MIHTile> tileList) {
ImageProvider logo = MzansiInnovationHub.of(context)!.theme.logoImage();
ImageProvider aiLogo = MzansiInnovationHub.of(context)!.theme.aiLogoImage(); ImageProvider aiLogo = MzansiInnovationHub.of(context)!.theme.aiLogoImage();
tileList.add(MIHTile( // tileList.add(MIHTile(
videoID: "P2bM9eosJ_A", // videoID: "P2bM9eosJ_A",
onTap: () { // onTap: () {
Navigator.of(context).pushNamed( // Navigator.of(context).pushNamed(
'/mzansi-profile', // '/mzansi-profile',
arguments: AppProfileUpdateArguments( // arguments: AppProfileUpdateArguments(
widget.signedInUser, // widget.signedInUser,
widget.propicFile, // widget.propicFile,
), // ),
); // );
}, // },
tileName: "Mzansi Profile", // tileName: "Mzansi Profile",
tileIcon: Padding( // tileIcon: Padding(
padding: const EdgeInsets.all(15.0), // padding: const EdgeInsets.all(15.0),
child: Image(image: logo), // child: Image(image: logo),
), // ),
p: getPrim(), // p: getPrim(),
s: getSec(), // s: getSec(),
)); // ));
tileList.add(MIHTile( tileList.add(MIHTile(
videoID: "6l8h0sjt08k", videoID: "6l8h0sjt08k",
onTap: () { onTap: () {

View File

@@ -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<Business?> favouriteBusinesses;
final String? myLocation;
const BuildFavouriteBusinessesList({
super.key,
required this.favouriteBusinesses,
required this.myLocation,
});
@override
State<BuildFavouriteBusinessesList> createState() =>
_BuildFavouriteBusinessesListState();
}
class _BuildFavouriteBusinessesListState
extends State<BuildFavouriteBusinessesList> {
@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),
),
),
);
},
);
}
}

View File

@@ -1,9 +1,12 @@
import 'package:flutter/material.dart'; 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_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.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_action.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tools.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_packages/mzansi_directory/package_tools/mih_search_mzansi.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_location_services.dart';
class MzansiDirectory extends StatefulWidget { class MzansiDirectory extends StatefulWidget {
final MzansiDirectoryArguments arguments; final MzansiDirectoryArguments arguments;
@@ -18,9 +21,22 @@ class MzansiDirectory extends StatefulWidget {
class _MzansiDirectoryState extends State<MzansiDirectory> { class _MzansiDirectoryState extends State<MzansiDirectory> {
int _selcetedIndex = 0; int _selcetedIndex = 0;
late Future<Position?> futurePosition =
MIHLocationAPI().getGPSPosition(context);
@override
void initState() {
super.initState();
if (widget.arguments.packageIndex == null) {
_selcetedIndex = 0;
} else {
_selcetedIndex = widget.arguments.packageIndex!;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
print('MzansiDirectory build method called!');
return MihPackage( return MihPackage(
appActionButton: getAction(), appActionButton: getAction(),
appTools: getTools(), appTools: getTools(),
@@ -37,12 +53,41 @@ class _MzansiDirectoryState extends State<MzansiDirectory> {
List<Widget> getToolBody() { List<Widget> getToolBody() {
List<Widget> toolBodies = [ List<Widget> toolBodies = [
MihSearchMzansi( FutureBuilder(
startUpSearch: widget.arguments.startUpSearch, future: futurePosition,
personalSearch: widget.arguments.personalSearch, 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(), // 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; return toolBodies;
} }
@@ -70,11 +115,11 @@ class _MzansiDirectoryState extends State<MzansiDirectory> {
// _selcetedIndex = 1; // _selcetedIndex = 1;
// }); // });
// }; // };
// temp[const Icon(Icons.business_center)] = () { temp[const Icon(Icons.business_center)] = () {
// setState(() { setState(() {
// _selcetedIndex = 2; _selcetedIndex = 1;
// }); });
// }; };
return MihPackageTools( return MihPackageTools(
tools: temp, tools: temp,
selcetedIndex: _selcetedIndex, selcetedIndex: _selcetedIndex,
@@ -84,8 +129,8 @@ class _MzansiDirectoryState extends State<MzansiDirectory> {
List<String> getToolTitle() { List<String> getToolTitle() {
List<String> toolTitles = [ List<String> toolTitles = [
"Mzansi Search", "Mzansi Search",
"Contacts",
"Favourite Businesses", "Favourite Businesses",
"Contacts",
]; ];
return toolTitles; return toolTitles;
} }

View File

@@ -23,8 +23,8 @@ class _MzansiDirectoryTileState extends State<MzansiDirectoryTile> {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
'/mzansi-directory', '/mzansi-directory',
arguments: MzansiDirectoryArguments( arguments: MzansiDirectoryArguments(
null, // startUpSearch personalSearch: true,
true, // personalSearch startSearchText: null,
), ),
); );
}, },

View File

@@ -1,11 +1,24 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/main.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_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_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_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 { class MihFavouriteBusinesses extends StatefulWidget {
const MihFavouriteBusinesses({super.key}); final String? myLocation;
const MihFavouriteBusinesses({
super.key,
required this.myLocation,
});
@override @override
State<MihFavouriteBusinesses> createState() => _MihFavouriteBusinessesState(); State<MihFavouriteBusinesses> createState() => _MihFavouriteBusinessesState();
@@ -15,6 +28,70 @@ class _MihFavouriteBusinessesState extends State<MihFavouriteBusinesses> {
final TextEditingController businessSearchController = final TextEditingController businessSearchController =
TextEditingController(); TextEditingController();
final FocusNode searchFocusNode = FocusNode(); final FocusNode searchFocusNode = FocusNode();
late Future<List<BookmarkedBusiness>> boookmarkedBusinessListFuture;
List<BookmarkedBusiness> listBookmarkedBusinesses = [];
final ValueNotifier<List<Business?>> searchBookmarkedBusinesses =
ValueNotifier([]);
late Future<Map<String, Business?>> businessDetailsMapFuture;
Map<String, Business?> _businessDetailsMap = {};
Timer? _debounce;
Future<Map<String, Business?>>
getAndMapAllBusinessDetailsForBookmarkedBusinesses() async {
String user_id = await SuperTokens.getUserId();
List<BookmarkedBusiness> bookmarked = await MihMzansiDirectoryServices()
.getAllUserBookmarkedBusiness(user_id);
listBookmarkedBusinesses = bookmarked;
Map<String, Business?> businessMap = {};
List<Future<Business?>> detailFutures = [];
for (var item in bookmarked) {
detailFutures.add(MihBusinessDetailsServices()
.getBusinessDetailsByBusinessId(item.business_id));
}
List<Business?> 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<Business?> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -44,6 +121,130 @@ class _MihFavouriteBusinessesState extends State<MihFavouriteBusinesses> {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
FutureBuilder<Map<String, Business?>>(
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<List<Business?>>(
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."),
);
}
}),
], ],
), ),
); );

View File

@@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:mzansi_innovation_hub/main.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/app_user.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/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_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_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_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_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_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_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_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_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'; import 'package:mzansi_innovation_hub/mih_services/mih_user_services.dart';
class MihSearchMzansi extends StatefulWidget { class MihSearchMzansi extends StatefulWidget {
final String? startUpSearch;
final bool personalSearch; final bool personalSearch;
final String? myLocation;
final String? startSearchText;
const MihSearchMzansi({ const MihSearchMzansi({
super.key, super.key,
required this.startUpSearch,
required this.personalSearch, required this.personalSearch,
required this.myLocation,
required this.startSearchText,
}); });
@override @override
@@ -29,14 +32,62 @@ class MihSearchMzansi extends StatefulWidget {
class _MihSearchMzansiState extends State<MihSearchMzansi> { class _MihSearchMzansiState extends State<MihSearchMzansi> {
final TextEditingController mzansiSearchController = TextEditingController(); final TextEditingController mzansiSearchController = TextEditingController();
final TextEditingController businessTypeController = TextEditingController();
final FocusNode searchFocusNode = FocusNode(); final FocusNode searchFocusNode = FocusNode();
late bool userSearch; late bool userSearch;
Future<List<AppUser>?> futureUserSearchResults = Future.value(); Future<List<AppUser>?> futureUserSearchResults = Future.value();
Future<List<Business>?> futureBusinessSearchResults = Future.value(); Future<List<Business>?> futureBusinessSearchResults = Future.value();
late Future<Position?> futurePosition =
MIHLocationAPI().getGPSPosition(context);
List<AppUser> userSearchResults = []; List<AppUser> userSearchResults = [];
List<Business> businessSearchResults = []; List<Business> businessSearchResults = [];
late Future<List<String>> 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 @override
void dispose() { void dispose() {
@@ -49,13 +100,13 @@ class _MihSearchMzansiState extends State<MihSearchMzansi> {
super.initState(); super.initState();
setState(() { setState(() {
userSearch = widget.personalSearch; userSearch = widget.personalSearch;
mzansiSearchController.text = widget.startUpSearch ?? ""; availableBusinessTypes =
if (userSearch) { MihBusinessDetailsServices().fetchAllBusinessTypes();
futureUserSearchResults = if (widget.startSearchText != null) {
MihUserServices().searchUsers(mzansiSearchController.text, context); mzansiSearchController.text = widget.startSearchText!;
searchPressed();
} else { } else {
futureBusinessSearchResults = MihBusinessDetailsServices() mzansiSearchController.text = "";
.searchBusinesses(mzansiSearchController.text, context);
} }
}); });
} }
@@ -76,76 +127,132 @@ class _MihSearchMzansiState extends State<MihSearchMzansi> {
children: [ children: [
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: width / 20), padding: EdgeInsets.symmetric(horizontal: width / 20),
child: MihSearchBar( child: Row(
controller: mzansiSearchController, mainAxisAlignment: MainAxisAlignment.center,
hintText: "Search Mzansi", children: [
prefixIcon: Icons.search, Expanded(
prefixAltIcon: userSearch ? Icons.person : Icons.business, child: MihSearchBar(
suffixTools: [ controller: mzansiSearchController,
IconButton( 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: () { onPressed: () {
if (filterOn) {
clearAll();
}
setState(() { setState(() {
// searchTypeVisibility = !searchTypeVisibility; filterOn = !filterOn;
userSearch = !userSearch;
if (userSearch) {
futureUserSearchResults = MihUserServices()
.searchUsers(
mzansiSearchController.text, context);
} else {
futureBusinessSearchResults =
MihBusinessDetailsServices().searchBusinesses(
mzansiSearchController.text, context);
}
}); });
}, },
icon: Icon( icon: Icon(
Icons.swap_horiz_rounded, !filterOn
? Icons.filter_list_rounded
: Icons.filter_list_off_rounded,
size: 35, size: 35,
color: color: MzansiInnovationHub.of(context)!
MzansiInnovationHub.of(context)!.theme.primaryColor(), .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), const SizedBox(height: 10),
FutureBuilder( FutureBuilder(
future: futurePosition, future: availableBusinessTypes,
builder: (context, asyncSnapshot) { builder: (context, asyncSnapshot) {
String myLocation = ""; List<String> options = [];
if (asyncSnapshot.connectionState == ConnectionState.waiting) { if (asyncSnapshot.connectionState == ConnectionState.done) {
myLocation = "Getting Your GPS Location Ready"; options.addAll(asyncSnapshot.data!);
} else {
myLocation = asyncSnapshot.data
.toString()
.replaceAll("Latitude: ", "")
.replaceAll("Longitude: ", "");
} }
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 ?? ""),
], ],
), ),
); );

View File

@@ -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<MihAddBookmarkAlert> createState() => _MihAddBookmarkAlertState();
}
class _MihAddBookmarkAlertState extends State<MihAddBookmarkAlert> {
Future<void> 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,
),
),
),
],
),
],
),
);
}
}

View File

@@ -1,38 +1,26 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/main.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.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/business_review.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_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_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_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:mzansi_innovation_hub/mih_services/mih_mzansi_directory_services.dart';
import 'package:redacted/redacted.dart';
import 'package:supertokens_flutter/supertokens.dart'; import 'package:supertokens_flutter/supertokens.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class MihBusinessCard extends StatefulWidget { class MihBusinessCard extends StatefulWidget {
final Business business; final Business business;
final String? startUpSearch; 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; final double width;
const MihBusinessCard({ const MihBusinessCard({
super.key, super.key,
required this.business, required this.business,
required this.startUpSearch, 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, required this.width,
}); });
@@ -41,6 +29,16 @@ class MihBusinessCard extends StatefulWidget {
} }
class _MihBusinessCardState extends State<MihBusinessCard> { class _MihBusinessCardState extends State<MihBusinessCard> {
Future<BusinessReview?>? _businessReviewFuture;
Future<BookmarkedBusiness?>? _bookmarkedBusinessFuture;
RedactedConfiguration getRedactedConfiguration() {
return RedactedConfiguration(
// redactedColor: Colors.pink,
redactedColor: MzansiInnovationHub.of(context)!.theme.primaryColor(),
);
}
Future<void> _makePhoneCall(String phoneNumber) async { Future<void> _makePhoneCall(String phoneNumber) async {
final Uri url = Uri(scheme: 'tel', path: phoneNumber); final Uri url = Uri(scheme: 'tel', path: phoneNumber);
if (await canLaunchUrl(url)) { if (await canLaunchUrl(url)) {
@@ -269,6 +267,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
String subLabel, String subLabel,
IconData icon, IconData icon,
Color? iconColor, Color? iconColor,
bool redacted,
Function()? ontap, Function()? ontap,
) { ) {
return Material( return Material(
@@ -288,16 +287,25 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
child: Row( child: Row(
children: [ children: [
Container( Container(
width: 45,
height: 45,
decoration: BoxDecoration( decoration: BoxDecoration(
color: iconColor, color: iconColor,
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
), ),
padding: const EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
child: Icon( child: FittedBox(
icon, child: Icon(
size: 35, icon,
color: MzansiInnovationHub.of(context)!.theme.primaryColor(), // size: 35,
color:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
),
), ),
).redacted(
context: context,
redact: redacted,
configuration: getRedactedConfiguration(),
), ),
SizedBox(width: 20), SizedBox(width: 20),
Expanded( Expanded(
@@ -316,6 +324,10 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
.primaryColor(), .primaryColor(),
height: 1.0, height: 1.0,
), ),
).redacted(
context: context,
redact: redacted,
configuration: getRedactedConfiguration(),
), ),
Text( Text(
subLabel, subLabel,
@@ -326,6 +338,10 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
.theme .theme
.primaryColor(), .primaryColor(),
), ),
).redacted(
context: context,
redact: redacted,
configuration: getRedactedConfiguration(),
), ),
], ],
), ),
@@ -345,6 +361,27 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
); );
} }
Future<BookmarkedBusiness?> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// double screenWidth = MediaQuery.of(context).size.width; // double screenWidth = MediaQuery.of(context).size.width;
@@ -369,6 +406,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
"Give us a quick call.", "Give us a quick call.",
Icons.phone, Icons.phone,
MihColors.getGreenColor(context), MihColors.getGreenColor(context),
false,
() { () {
// print("Calling ${widget.cellNumber}"); // print("Calling ${widget.cellNumber}");
_makePhoneCall(widget.business.contact_no); _makePhoneCall(widget.business.contact_no);
@@ -382,6 +420,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
"Send us an email.", "Send us an email.",
Icons.email, Icons.email,
MihColors.getPinkColor(context), MihColors.getPinkColor(context),
false,
() { () {
// print("Emailing ${widget.email}"); // print("Emailing ${widget.email}");
_launchEmail( _launchEmail(
@@ -391,58 +430,182 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
); );
}, },
), ),
Divider( Visibility(
color: MzansiInnovationHub.of(context)!.theme.primaryColor(), 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( Visibility(
"Location", visible: widget.business.website.isNotEmpty &&
"Come visit us.", widget.business.website != "",
Icons.location_on, child: Column(
MihColors.getOrangeColor(context), children: [
() { Divider(
final latitude = color:
double.parse(widget.business.gps_location.split(',')[0]); MzansiInnovationHub.of(context)!.theme.primaryColor(),
final longitude = ),
double.parse(widget.business.gps_location.split(',')[1]); _buildContactInfo(
_launchGoogleMapsWithUrl( "Website",
latitude: latitude, "Find out more about us.",
longitude: longitude, 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( FutureBuilder(
visible: widget.business.website.isNotEmpty && future: _bookmarkedBusinessFuture,
widget.business.website != "", builder: (context, asyncSnapshot) {
child: Divider( if (asyncSnapshot.connectionState == ConnectionState.waiting) {
color: MzansiInnovationHub.of(context)!.theme.primaryColor(), // return const SizedBox.shrink();
), return Column(
), children: [
Visibility( Padding(
visible: widget.business.website.isNotEmpty && padding: const EdgeInsets.symmetric(horizontal: 10.0),
widget.business.website != "", child: Divider(
child: _buildContactInfo( color: MzansiInnovationHub.of(context)!
"Website", .theme
"Find out more about us.", .primaryColor(),
Icons.vpn_lock, ),
MihColors.getRedColor(context), ),
() { Container(
_launchWebsite(widget.business.website); child: _buildContactInfo(
}, "Loading Bookmark",
), "Loading your bookmark.",
), Icons.bookmark_add_rounded,
Padding( MihColors.getBluishPurpleColor(context),
padding: const EdgeInsets.symmetric(horizontal: 10.0), true,
child: Divider( null,
color: MzansiInnovationHub.of(context)!.theme.primaryColor(), ),
), ),
), ],
_buildContactInfo( );
"Rate Us", } else {
"Let us know how we are doing.", BookmarkedBusiness? bookmarkBusiness = asyncSnapshot.data;
Icons.star_rate_rounded, String bookmarkDisplayTitle = "";
MihColors.getYellowColor(context), if (bookmarkBusiness == null) {
() { bookmarkDisplayTitle = "Bookmark Us";
businessReviewRatingWindow(true, widget.width); } 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( // Padding(
@@ -455,7 +618,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
// "Bookmark", // "Bookmark",
// "Save us for later.", // "Save us for later.",
// Icons.bookmark_add_rounded, // Icons.bookmark_add_rounded,
// MihColors.getBluishPurpleColor(context), // MihColors.getBluishPurpleColor(context),
// () { // () {
// // _launchWebsite(widget.website); // // _launchWebsite(widget.website);
// print("Saving ${widget.business.Name} to Directory"); // print("Saving ${widget.business.Name} to Directory");
@@ -476,115 +639,34 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
} }
Future<void> businessReviewRatingWindow( Future<void> businessReviewRatingWindow(
bool previouslyRated, double width) async { BusinessReview? myReview, bool previouslyRated, double width) async {
showDialog( showDialog(
context: context, context: context,
builder: (context) => FutureBuilder( builder: (context) => MihReviewBusinessWindow(
future: getUserReview(), business: widget.business,
builder: (context, asyncSnapshot) { businessReview: myReview,
if (asyncSnapshot.connectionState == ConnectionState.waiting) { screenWidth: width,
return const Mihloadingcircle( readOnly: false,
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,
),
),
],
),
);
}
},
), ),
); );
} }
void showBookmarkAlert() { void showAddBookmarkAlert() {
showDialog( showDialog(
context: context, context: context,
builder: (context) => MihPackageAlert( builder: (context) => MihAddBookmarkAlert(
alertColour: MihColors.getSecondaryColor(context), business: widget.business,
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,
),
),
),
],
),
],
),
), ),
); );
} }
void showDeleteBookmarkAlert(BookmarkedBusiness? bookmarkBusiness) {
showDialog(
context: context,
builder: (context) => MihDeleteBookmarkAlert(
business: widget.business,
bookmarkBusiness: bookmarkBusiness,
startUpSearch: widget.startUpSearch,
));
}
} }

View File

@@ -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<MihDeleteBookmarkAlert> createState() => _MihDeleteBookmarkAlertState();
}
class _MihDeleteBookmarkAlertState extends State<MihDeleteBookmarkAlert> {
Future<void> 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,
),
),
),
],
),
],
),
);
}
}

View File

@@ -23,14 +23,12 @@ class MihReviewBusinessWindow extends StatefulWidget {
final BusinessReview? businessReview; final BusinessReview? businessReview;
final double screenWidth; final double screenWidth;
final bool readOnly; final bool readOnly;
final String? startUpSearch;
const MihReviewBusinessWindow({ const MihReviewBusinessWindow({
super.key, super.key,
required this.business, required this.business,
required this.businessReview, required this.businessReview,
required this.screenWidth, required this.screenWidth,
required this.readOnly, required this.readOnly,
required this.startUpSearch,
}); });
@override @override
@@ -101,8 +99,8 @@ class _MihReviewBusinessWindowState extends State<MihReviewBusinessWindow> {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
'/mzansi-directory', '/mzansi-directory',
arguments: MzansiDirectoryArguments( arguments: MzansiDirectoryArguments(
widget.startUpSearch, // startUpSearch personalSearch: false, // personalSearch
false, // personalSearch startSearchText: widget.business.Name,
), ),
); );
MihAlertServices().successAlert( MihAlertServices().successAlert(
@@ -191,8 +189,8 @@ class _MihReviewBusinessWindowState extends State<MihReviewBusinessWindow> {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
'/mzansi-directory', '/mzansi-directory',
arguments: MzansiDirectoryArguments( arguments: MzansiDirectoryArguments(
widget.startUpSearch, // startUpSearch personalSearch: false, // personalSearch
false, // personalSearch startSearchText: widget.business.Name,
), ),
); );
MihAlertServices().successAlert( MihAlertServices().successAlert(
@@ -227,8 +225,8 @@ class _MihReviewBusinessWindowState extends State<MihReviewBusinessWindow> {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
'/mzansi-directory', '/mzansi-directory',
arguments: MzansiDirectoryArguments( arguments: MzansiDirectoryArguments(
widget.startUpSearch, // startUpSearch personalSearch: false, // personalSearch
false, // personalSearch startSearchText: widget.business.Name,
), ),
); );
MihAlertServices().successAlert( MihAlertServices().successAlert(
@@ -386,7 +384,8 @@ class _MihReviewBusinessWindowState extends State<MihReviewBusinessWindow> {
emptyColor: MzansiInnovationHub.of(context)! emptyColor: MzansiInnovationHub.of(context)!
.theme .theme
.secondaryColor(), .secondaryColor(),
halfFilledColor: MihColors.getYellowColor(context), isHalfAllowed: true, halfFilledColor: MihColors.getYellowColor(context),
isHalfAllowed: true,
initialRating: widget.businessReview != null initialRating: widget.businessReview != null
? double.parse(_reviewScoreController.text) ? double.parse(_reviewScoreController.text)
: 1, : 1,

View File

@@ -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_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.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_action.dart';
@@ -98,6 +99,11 @@ class _MzansiBusinessProfileState extends State<MzansiBusinessProfile> {
_selcetedIndex = 3; _selcetedIndex = 3;
}); });
}; };
temp[const Icon(Icons.star_rate_rounded)] = () {
setState(() {
_selcetedIndex = 4;
});
};
return MihPackageTools( return MihPackageTools(
tools: temp, tools: temp,
selcetedIndex: _selcetedIndex, selcetedIndex: _selcetedIndex,
@@ -149,6 +155,7 @@ class _MzansiBusinessProfileState extends State<MzansiBusinessProfile> {
// MihBusinessProfile(arguments: widget.arguments), // MihBusinessProfile(arguments: widget.arguments),
MihMyBusinessTeam(arguments: widget.arguments), MihMyBusinessTeam(arguments: widget.arguments),
MihBusinessUserSearch(arguments: widget.arguments), MihBusinessUserSearch(arguments: widget.arguments),
MihBusinessReviews(business: widget.arguments.business!),
]; ];
return toolBodies; return toolBodies;
} }
@@ -159,6 +166,7 @@ class _MzansiBusinessProfileState extends State<MzansiBusinessProfile> {
"User", "User",
"Team", "Team",
"Add Member", "Add Member",
"Reviews",
]; ];
return toolTitles; return toolTitles;
} }

View File

@@ -40,7 +40,6 @@ class _MihBusinessReviewsState extends State<MihBusinessReviews> {
businessReview: businessReview, businessReview: businessReview,
screenWidth: width, screenWidth: width,
readOnly: true, readOnly: true,
startUpSearch: null,
); );
}, },
); );
@@ -59,6 +58,7 @@ class _MihBusinessReviewsState extends State<MihBusinessReviews> {
} else if (asyncSnapshot.connectionState == ConnectionState.done && } else if (asyncSnapshot.connectionState == ConnectionState.done &&
asyncSnapshot.hasData) { asyncSnapshot.hasData) {
List<BusinessReview> reviews = asyncSnapshot.data!; List<BusinessReview> reviews = asyncSnapshot.data!;
print("Reviews: ${reviews.length}");
if (reviews.isEmpty) { if (reviews.isEmpty) {
return Column( return Column(
children: [ children: [
@@ -133,11 +133,16 @@ class _MihBusinessReviewsState extends State<MihBusinessReviews> {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
Text( Visibility(
"${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 ? "..." : ""}", visible: reviews[index].rating_description.isNotEmpty,
style: TextStyle( child: Text(
fontSize: 18, reviews[index].rating_description.isEmpty
fontWeight: FontWeight.normal, ? ""
: "${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( Text(

View File

@@ -150,6 +150,9 @@ class _MihCardDisplayState extends State<MihCardDisplay> {
case "fresmart": case "fresmart":
return Image.asset( return Image.asset(
'lib/mih_components/mih_package_components/assets/images/loyalty_cards/mini/fresmart-min.png'); '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: default:
return null; return null;
} }

View File

@@ -161,6 +161,7 @@ class _MihCardsState extends State<MihCards> {
"Spar", "Spar",
"Spur", "Spur",
"TFG Group", "TFG Group",
"Total Energies",
"Toys R Us", "Toys R Us",
"Woermann Brock", "Woermann Brock",
"Woolworths" "Woolworths"
@@ -367,7 +368,7 @@ class _MihCardsState extends State<MihCards> {
builder: (BuildContext context, builder: (BuildContext context,
List<MIHLoyaltyCard> value, Widget? child) { List<MIHLoyaltyCard> value, Widget? child) {
return BuildLoyaltyCardList( return BuildLoyaltyCardList(
cardList: searchShopName.value, cardList: value,
signedInUser: widget.signedInUser, signedInUser: widget.signedInUser,
navIndex: 0, navIndex: 0,
bannerAd: _bannerAd, bannerAd: _bannerAd,

View File

@@ -9,12 +9,42 @@ import '../mih_components/mih_pop_up_messages/mih_error_message.dart';
import 'package:supertokens_flutter/http.dart' as http; import 'package:supertokens_flutter/http.dart' as http;
class MihBusinessDetailsServices { class MihBusinessDetailsServices {
Future<List<String>> fetchAllBusinessTypes() async {
var response = await http.get(
Uri.parse("${AppEnviroment.baseApiUrl}/business/types/"),
headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8"
},
);
if (response.statusCode == 200) {
List<dynamic> jsonList = jsonDecode(response.body);
List<String> businessTypes =
jsonList.map((item) => item['type'].toString()).toList();
return businessTypes;
} else {
return [];
}
}
Future<List<Business>> searchBusinesses( Future<List<Business>> searchBusinesses(
String searchText, String searchText,
String searchType,
BuildContext context, BuildContext context,
) async { ) 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( var response = await http.get(
Uri.parse("${AppEnviroment.baseApiUrl}/businesses/search/$searchText"), Uri.parse(
"${AppEnviroment.baseApiUrl}/business/search/$newSearchType/$newSearchText"),
headers: <String, String>{ headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8" "Content-Type": "application/json; charset=UTF-8"
}, },
@@ -29,7 +59,7 @@ class MihBusinessDetailsServices {
} }
} }
Future<Business?> getBusinessDetails( Future<Business?> getBusinessDetailsByUser(
String app_id, String app_id,
) async { ) async {
var response = await http.get( var response = await http.get(
@@ -47,6 +77,25 @@ class MihBusinessDetailsServices {
} }
} }
Future<Business?> getBusinessDetailsByBusinessId(
String business_id,
) async {
var response = await http.get(
Uri.parse(
"${AppEnviroment.baseApiUrl}/business/business_id/$business_id"),
headers: <String, String>{
"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<Response> createBusinessDetails( Future<Response> createBusinessDetails(
String appId, String appId,
String busineName, String busineName,

View File

@@ -1,4 +1,5 @@
import 'dart:convert'; 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_components/mih_objects/business_review.dart';
import 'package:mzansi_innovation_hub/mih_config/mih_env.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_env.dart';
import 'package:supertokens_flutter/http.dart' as http; import 'package:supertokens_flutter/http.dart' as http;
@@ -6,6 +7,10 @@ import 'package:supertokens_flutter/http.dart' as http;
class MihMzansiDirectoryServices { class MihMzansiDirectoryServices {
final baseAPI = AppEnviroment.baseApiUrl; final baseAPI = AppEnviroment.baseApiUrl;
//########################################################
//# Business Ratings #
//########################################################
Future<BusinessReview?> getUserReviewOfBusiness( Future<BusinessReview?> getUserReviewOfBusiness(
String app_id, String app_id,
String business_id, String business_id,
@@ -33,10 +38,9 @@ class MihMzansiDirectoryServices {
List<BusinessReview> businessReviews = List<BusinessReview>.from( List<BusinessReview> businessReviews = List<BusinessReview>.from(
l.map((model) => BusinessReview.fromJson(model))); l.map((model) => BusinessReview.fromJson(model)));
return businessReviews; return businessReviews;
} else if (response.statusCode == 404){ } else if (response.statusCode == 404) {
return []; return [];
} } else {
else {
throw Exception('failed to fetch Business Reviews'); throw Exception('failed to fetch Business Reviews');
} }
} }
@@ -128,4 +132,84 @@ class MihMzansiDirectoryServices {
return response.statusCode; return response.statusCode;
} }
} }
//########################################################
//# Bookmarked Business #
//########################################################
Future<BookmarkedBusiness?> 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<List<BookmarkedBusiness>> 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<BookmarkedBusiness> businessReviews = List<BookmarkedBusiness>.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<int> addBookmarkedBusiness(
String app_id,
String business_id,
) async {
var response = await http.post(
Uri.parse(
"${AppEnviroment.baseApiUrl}/mzansi-directory/bookmarked-business/insert/"),
headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8"
},
body: jsonEncode(<String, dynamic>{
"app_id": app_id,
"business_id": business_id,
}),
);
if (response.statusCode == 201) {
return response.statusCode;
} else {
return response.statusCode;
}
}
Future<int> deleteBookmarkedBusiness(
int idbookmarked_businesses,
) async {
var response = await http.delete(
Uri.parse(
"${AppEnviroment.baseApiUrl}/mzansi-directory/bookmarked-business/delete/"),
headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8"
},
body: jsonEncode(<String, dynamic>{
"idbookmarked_businesses": idbookmarked_businesses,
}),
);
if (response.statusCode == 200) {
return response.statusCode;
} else {
return response.statusCode;
}
}
} }

View File

@@ -78,7 +78,8 @@ class MIHApiCalls {
} }
// Get Businessdata // Get Businessdata
Business? business = await MihBusinessDetailsServices().getBusinessDetails( Business? business =
await MihBusinessDetailsServices().getBusinessDetailsByUser(
uid, uid,
); );
if (business != null) { if (business != null) {

View File

@@ -5,7 +5,7 @@ WORKDIR /app
COPY requirements.txt ./ COPY requirements.txt ./
RUN --mount=type=cache,target=/root/.cache/pip \ RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt pip --default-timeout=120 install -r requirements.txt
# COPY . ./app # COPY . ./app

View File

@@ -64,10 +64,21 @@ class BusinessRating(Base):
rating_description = Column(String(256), nullable=False, server_default="") rating_description = Column(String(256), nullable=False, server_default="")
rating_score = Column(String(45), nullable=False, server_default="") rating_score = Column(String(45), nullable=False, server_default="")
date_time = Column(DateTime, nullable=True) date_time = Column(DateTime, nullable=True)
def __repr__(self): def __repr__(self):
return ( return (
f"<BusinessRating(idbusiness_ratings={self.idbusiness_ratings}, app_id='{self.app_id}', " f"<BusinessRating(idbusiness_ratings={self.idbusiness_ratings}, app_id='{self.app_id}', "
f"business_id='{self.business_id}', rating_title='{self.rating_title}', rating_description='{self.rating_description}', " f"business_id='{self.business_id}', rating_title='{self.rating_title}', rating_description='{self.rating_description}', "
f"rating_score='{self.rating_score}', date_time='{self.date_time}')>" f"rating_score='{self.rating_score}', date_time='{self.date_time}')>"
)
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"<BusinessRating(idbookmarked_businesses={self.idbookmarked_businesses}, app_id='{self.app_id}', "
f"business_id='{self.business_id}', created_date='{self.created_date}')>"
) )

View File

@@ -1,7 +1,11 @@
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel from pydantic import BaseModel
#from ..mih_database import dbConnection #from ..mih_database import dbConnection
import mih_database 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 #SuperToken Auth from front end
from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.session import SessionContainer
@@ -60,90 +64,162 @@ class businessUpdateRequestV2(BaseModel):
# Get List of all files # Get List of all files
@router.get("/businesses/search/{search}", tags=["MIH Business"]) @router.get("/business/types/", tags=["MIH Business"])
async def read_all_businesses(search: str, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) async def read_business_by_business_id(session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session())
db = mih_database.dbConnection.dbAppDataConnect() dbEngine = mih_database.mihDbConnections.dbAllConnect()
cursor = db.cursor() dbSession = Session(dbEngine)
query = "SELECT business.business_id, business.Name, business.type, business.registration_no, " try:
query += "business.logo_name, business.logo_path, business.contact_no, business.bus_email, " queryResults = dbSession.query(Business.type).distinct().order_by(Business.type).all()
query += "business.gps_location, " response_data = [{"type": t[0]} for t in queryResults]
query += "practice_no, vat_no, " return response_data
query += "website, rating, mission_vision " except Exception as e:
query += "FROM business " print(f"An error occurred during the ORM query: {e}")
query += "WHERE LOWER(business.Name) LIKE %s OR LOWER(business.type) LIKE %s " if dbSession.is_active:
query += "OR LOWER(business.bus_email) LIKE %s OR LOWER(business.mission_vision) LIKE %s" dbSession.rollback()
search_term = f"%{search.lower()}%" # Add wildcards and lowercase raise HTTPException(
cursor.execute(query, (search_term, search_term, search_term, search_term)) status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
items = [ detail="Failed to retrieve records due to an internal server error."
{ )
"business_id": item[0], finally:
"Name": item[1], dbSession.close()
"type": item[2],
"registration_no": item[3], # Get List of all files
"logo_name": item[4], @router.get("/business/search/{type}/{search}", tags=["MIH Business"])
"logo_path": item[5], async def read_all_businesses(search: str, type: str, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session())
"contact_no": item[6], dbEngine = mih_database.mihDbConnections.dbAllConnect()
"bus_email": item[7], dbSession = Session(dbEngine)
"app_id": "", try:
"gps_location": item[8], queryResults = dbSession.query(Business)
"practice_no": item[9], type_term_with_wildcards = ""
"vat_no": item[10], if type != "All":
"website": item[11], type_term_with_wildcards = f"%{type.lower()}%"
"rating": item[12], queryResults = queryResults.filter(
"mission_vision": item[13], Business.type.ilike(type_term_with_wildcards)
} )
for item in cursor.fetchall() search_term_with_wildcards = ""
] if search != "All":
cursor.close() search_term_with_wildcards = f"%{search.lower()}%"
db.close() queryResults = queryResults.filter(
return items 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 # Get List of all files
@router.get("/business/business_id/{business_id}", tags=["MIH Business"]) @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()) 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() dbEngine = mih_database.mihDbConnections.dbAllConnect()
cursor = db.cursor() dbSession = Session(dbEngine)
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"
try: try:
cursor.execute(query, (business_id,)) queryResults = dbSession.query(Business).\
except Exception as error: filter(
raise HTTPException(status_code=404, detail="Failed to pull records") Business.business_id == business_id,
items = [ ).first()
{ if queryResults:
"business_id": item[0], return {
"Name": item[1], "business_id": queryResults.business_id,
"type": item[2], "Name": queryResults.Name,
"registration_no": item[3], "type": queryResults.type,
"logo_name": item[4], "registration_no": queryResults.registration_no,
"logo_path": item[5], "logo_name": queryResults.logo_name,
"contact_no": item[6], "logo_path": queryResults.logo_path,
"bus_email": item[7], "contact_no": queryResults.contact_no,
"app_id": item[8], "bus_email": queryResults.bus_email,
"gps_location": item[9], "app_id": "",
"practice_no": item[10], "gps_location": queryResults.gps_location,
"vat_no": item[11], "practice_no": queryResults.practice_no,
"website": item[12], "vat_no": queryResults.vat_no,
"rating": item[13], "website": queryResults.website,
"mission_vision": item[14], "rating": queryResults.rating,
} "mission_vision": queryResults.mission_vision,
for item in cursor.fetchall() }
] else:
# raise HTTPException(
cursor.close() status_code=status.HTTP_404_NOT_FOUND,
db.close() detail="Business not found for the given business_id."
if(len(items)!= 0): )
return items[0] except HTTPException as http_exc:
else: # Re-raise HTTPException directly if it was raised within the try block
raise HTTPException(status_code=404, detail="No record found") 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 # Get List of all files
@router.get("/business/app_id/{app_id}", tags=["MIH Business"]) @router.get("/business/app_id/{app_id}", tags=["MIH Business"])

View File

@@ -4,7 +4,7 @@ from sqlalchemy.orm import Session
from pydantic import BaseModel from pydantic import BaseModel
from datetime import datetime from datetime import datetime
import mih_database.mihDbConnections 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.framework.fastapi import verify_session
from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.session import SessionContainer
from fastapi import Depends from fastapi import Depends
@@ -40,6 +40,17 @@ class BusinessRatingUpdateRequest(BaseModel):
rating_old_score: str rating_old_score: str
current_rating: 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"]) @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()) 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() dbEngine = mih_database.mihDbConnections.dbAllConnect()
@@ -295,4 +306,154 @@ async def UpdatePatient(itemRequest : BusinessRatingUpdateRequest, session: Sess
) )
finally: finally:
dbSession.close() dbSession.close()
return {"message": "Successfully wUpdated Record"} 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"}