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;
theme.setScreenType(width);
precacheImage(theme.loadingImage(), context);
precacheImage(theme.logoImage(), context);
precacheImage(theme.logoFrame(), context);
return MaterialApp(
title: getTitle(),
themeMode: ThemeMode.dark,

View File

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

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,
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_personal_profile_preview.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/components/mih_business_info_card.dart';
// import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/components/mih_business_info_card.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_alert_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_location_services.dart';
@@ -30,6 +31,7 @@ import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_text_form_field.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_time_field.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_toggle.dart';
import 'package:redacted/redacted.dart';
class PackageToolOne extends StatefulWidget {
final AppUser user;
@@ -291,16 +293,34 @@ class _PackageToolOneState extends State<PackageToolOne> {
}
}),
const SizedBox(height: 10),
// MihBusinessCard(
// businessid: "123456",
// businessName: "Mzansi Innovation Hub",
// cellNumber: "0788300006",
// email: "yasien.meth@mzansi-innovation-hub.co.za",
// gpsLocation: "-26.1853611, 28.134664",
// website:
// "https://app.mzansi-innovation-hub.co.za/privacy.html",
// rating: 3.25,
// ),
Text("This text should be redacted").redacted(
context: context,
redact: true,
),
MihBusinessCard(
business: Business(
"business_id",
"Name",
"type",
"registration_no",
"logo_name",
"logo_path",
"contact_no",
"bus_email",
"app_id",
"gps_location",
"practice_no",
"vat_no",
"website",
"rating",
"mission_vision",
),
startUpSearch: '',
width: 300,
).redacted(
context: context,
redact: true,
),
const SizedBox(height: 10),
Divider(
color:

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 =
currentPage + 1 < widget.appBody.length ? currentPage + 1 : currentPage;
if (nextPage != currentPage) {
await Future.delayed(const Duration(milliseconds: 300));
await Future.delayed(const Duration(milliseconds: 100));
await _pageController.animateTo(
currentOffset + peakOffset,
duration: const Duration(milliseconds: 300),
@@ -86,10 +86,24 @@ class _MihPackageState extends State<MihPackage>
vsync: this,
duration: const Duration(milliseconds: 400),
);
// if (!MzansiInnovationHub.of(context)!.theme.kIsWeb) {
// // Trigger the peak animation on start (or call this elsewhere)
// WidgetsBinding.instance.addPostFrameCallback((_) {
// _peakAnimation();
// });
// }
if (!MzansiInnovationHub.of(context)!.theme.kIsWeb) {
// Trigger the peak animation on start (or call this elsewhere)
// Trigger the peak animation only AFTER the route transition is complete
WidgetsBinding.instance.addPostFrameCallback((_) {
_peakAnimation();
final ModalRoute? currentRoute = ModalRoute.of(context);
if (currentRoute != null) {
currentRoute.animation?.addStatusListener((status) {
if (status == AnimationStatus.completed && mounted) {
// Ensure the widget is still mounted and the animation is completed
_peakAnimation();
}
});
}
});
}
}

View File

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

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

View File

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

View File

@@ -61,35 +61,6 @@ class _MIHAppDrawerState extends State<MIHAppDrawer> {
backgroundColor:
MzansiInnovationHub.of(context)!.theme.secondaryColor(),
),
// MIHProfilePicture(
// profilePictureFile: widget.propicFile,
// proPicController: proPicController,
// proPic: null,
// width: 60,
// radius: 27,
// drawerMode: true,
// editable: false,
// frameColor: MzansiInnovationHub.of(context)!.theme.primaryColor(),
// onChange: (newProPic) {},
// ),
// Stack(
// alignment: Alignment.center,
// fit: StackFit.loose,
// children: [
// CircleAvatar(
// backgroundColor:
// MzansiInnovationHub.of(context)!.theme.primaryColor(),
// backgroundImage: widget.propicFile,
// //'https://media.licdn.com/dms/image/D4D03AQGd1-QhjtWWpA/profile-displayphoto-shrink_400_400/0/1671698053061?e=2147483647&v=beta&t=a3dJI5yxs5-KeXjj10LcNCFuC9IOfa8nNn3k_Qyr0CA'),
// radius: 27,
// ),
// SizedBox(
// width: 60,
// child: Image(image: logoFrame),
// )
// ],
// ),
);
}

View File

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

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:geolocator/geolocator.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/arguments.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_action.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tools.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/package_tools/mih_favourite_businesses.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/package_tools/mih_search_mzansi.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_location_services.dart';
class MzansiDirectory extends StatefulWidget {
final MzansiDirectoryArguments arguments;
@@ -18,9 +21,22 @@ class MzansiDirectory extends StatefulWidget {
class _MzansiDirectoryState extends State<MzansiDirectory> {
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
Widget build(BuildContext context) {
print('MzansiDirectory build method called!');
return MihPackage(
appActionButton: getAction(),
appTools: getTools(),
@@ -37,12 +53,41 @@ class _MzansiDirectoryState extends State<MzansiDirectory> {
List<Widget> getToolBody() {
List<Widget> toolBodies = [
MihSearchMzansi(
startUpSearch: widget.arguments.startUpSearch,
personalSearch: widget.arguments.personalSearch,
),
FutureBuilder(
future: futurePosition,
builder: (context, asyncSnapshot) {
String myLocation = "";
if (asyncSnapshot.connectionState == ConnectionState.waiting) {
myLocation = "Getting Your GPS Location Ready";
} else {
myLocation = asyncSnapshot.data
.toString()
.replaceAll("Latitude: ", "")
.replaceAll("Longitude: ", "");
}
return MihSearchMzansi(
personalSearch: widget.arguments.personalSearch,
myLocation: myLocation,
startSearchText: widget.arguments.startSearchText,
);
}),
// MihContacts(),
// MihFavouriteBusinesses(),
FutureBuilder(
future: futurePosition,
builder: (context, asyncSnapshot) {
String myLocation = "";
if (asyncSnapshot.connectionState == ConnectionState.waiting) {
myLocation = "Getting Your GPS Location Ready";
} else {
myLocation = asyncSnapshot.data
.toString()
.replaceAll("Latitude: ", "")
.replaceAll("Longitude: ", "");
}
return MihFavouriteBusinesses(
myLocation: myLocation,
);
}),
];
return toolBodies;
}
@@ -70,11 +115,11 @@ class _MzansiDirectoryState extends State<MzansiDirectory> {
// _selcetedIndex = 1;
// });
// };
// temp[const Icon(Icons.business_center)] = () {
// setState(() {
// _selcetedIndex = 2;
// });
// };
temp[const Icon(Icons.business_center)] = () {
setState(() {
_selcetedIndex = 1;
});
};
return MihPackageTools(
tools: temp,
selcetedIndex: _selcetedIndex,
@@ -84,8 +129,8 @@ class _MzansiDirectoryState extends State<MzansiDirectory> {
List<String> getToolTitle() {
List<String> toolTitles = [
"Mzansi Search",
"Contacts",
"Favourite Businesses",
"Contacts",
];
return toolTitles;
}

View File

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

View File

@@ -1,11 +1,24 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/main.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/bookmarked_business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_search_bar.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_single_child_scroll.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/builders/build_favourite_businesses_list.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_business_details_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_mzansi_directory_services.dart';
import 'package:supertokens_flutter/supertokens.dart';
class MihFavouriteBusinesses extends StatefulWidget {
const MihFavouriteBusinesses({super.key});
final String? myLocation;
const MihFavouriteBusinesses({
super.key,
required this.myLocation,
});
@override
State<MihFavouriteBusinesses> createState() => _MihFavouriteBusinessesState();
@@ -15,6 +28,70 @@ class _MihFavouriteBusinessesState extends State<MihFavouriteBusinesses> {
final TextEditingController businessSearchController =
TextEditingController();
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
Widget build(BuildContext context) {
@@ -44,6 +121,130 @@ class _MihFavouriteBusinessesState extends State<MihFavouriteBusinesses> {
),
),
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:geolocator/geolocator.dart';
import 'package:mzansi_innovation_hub/main.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/app_user.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_dropdwn_field.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_icons.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_search_bar.dart';
@@ -10,17 +11,19 @@ import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_
import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/builders/build_business_search_resultsList.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/builders/build_user_search_results_list.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_alert_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_business_details_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_location_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_user_services.dart';
class MihSearchMzansi extends StatefulWidget {
final String? startUpSearch;
final bool personalSearch;
final String? myLocation;
final String? startSearchText;
const MihSearchMzansi({
super.key,
required this.startUpSearch,
required this.personalSearch,
required this.myLocation,
required this.startSearchText,
});
@override
@@ -29,14 +32,62 @@ class MihSearchMzansi extends StatefulWidget {
class _MihSearchMzansiState extends State<MihSearchMzansi> {
final TextEditingController mzansiSearchController = TextEditingController();
final TextEditingController businessTypeController = TextEditingController();
final FocusNode searchFocusNode = FocusNode();
late bool userSearch;
Future<List<AppUser>?> futureUserSearchResults = Future.value();
Future<List<Business>?> futureBusinessSearchResults = Future.value();
late Future<Position?> futurePosition =
MIHLocationAPI().getGPSPosition(context);
List<AppUser> userSearchResults = [];
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
void dispose() {
@@ -49,13 +100,13 @@ class _MihSearchMzansiState extends State<MihSearchMzansi> {
super.initState();
setState(() {
userSearch = widget.personalSearch;
mzansiSearchController.text = widget.startUpSearch ?? "";
if (userSearch) {
futureUserSearchResults =
MihUserServices().searchUsers(mzansiSearchController.text, context);
availableBusinessTypes =
MihBusinessDetailsServices().fetchAllBusinessTypes();
if (widget.startSearchText != null) {
mzansiSearchController.text = widget.startSearchText!;
searchPressed();
} else {
futureBusinessSearchResults = MihBusinessDetailsServices()
.searchBusinesses(mzansiSearchController.text, context);
mzansiSearchController.text = "";
}
});
}
@@ -76,76 +127,132 @@ class _MihSearchMzansiState extends State<MihSearchMzansi> {
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: width / 20),
child: MihSearchBar(
controller: mzansiSearchController,
hintText: "Search Mzansi",
prefixIcon: Icons.search,
prefixAltIcon: userSearch ? Icons.person : Icons.business,
suffixTools: [
IconButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: MihSearchBar(
controller: mzansiSearchController,
hintText: "Search Mzansi",
prefixIcon: Icons.search,
prefixAltIcon: userSearch ? Icons.person : Icons.business,
suffixTools: [
IconButton(
onPressed: () {
swapPressed();
},
icon: Icon(
Icons.swap_horiz_rounded,
size: 35,
color: MzansiInnovationHub.of(context)!
.theme
.primaryColor(),
),
),
],
fillColor:
MzansiInnovationHub.of(context)!.theme.secondaryColor(),
hintColor:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
onPrefixIconTap: () {
searchPressed();
},
onClearIconTap: () {
clearAll();
},
searchFocusNode: searchFocusNode,
),
),
Visibility(
visible: !userSearch,
child: const SizedBox(width: 10),
),
Visibility(
visible: !userSearch,
child: IconButton(
onPressed: () {
if (filterOn) {
clearAll();
}
setState(() {
// searchTypeVisibility = !searchTypeVisibility;
userSearch = !userSearch;
if (userSearch) {
futureUserSearchResults = MihUserServices()
.searchUsers(
mzansiSearchController.text, context);
} else {
futureBusinessSearchResults =
MihBusinessDetailsServices().searchBusinesses(
mzansiSearchController.text, context);
}
filterOn = !filterOn;
});
},
icon: Icon(
Icons.swap_horiz_rounded,
!filterOn
? Icons.filter_list_rounded
: Icons.filter_list_off_rounded,
size: 35,
color:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
))
color: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
),
),
),
],
fillColor:
MzansiInnovationHub.of(context)!.theme.secondaryColor(),
hintColor: MzansiInnovationHub.of(context)!.theme.primaryColor(),
onPrefixIconTap: () {
if (userSearch) {
setState(() {
futureUserSearchResults = MihUserServices()
.searchUsers(mzansiSearchController.text, context);
});
} else {
setState(() {
futureBusinessSearchResults = MihBusinessDetailsServices()
.searchBusinesses(mzansiSearchController.text, context);
});
}
},
onClearIconTap: () {
setState(() {
futureUserSearchResults = Future.value();
futureBusinessSearchResults = Future.value();
mzansiSearchController.clear();
});
},
searchFocusNode: searchFocusNode,
),
),
const SizedBox(height: 10),
FutureBuilder(
future: futurePosition,
future: availableBusinessTypes,
builder: (context, asyncSnapshot) {
String myLocation = "";
if (asyncSnapshot.connectionState == ConnectionState.waiting) {
myLocation = "Getting Your GPS Location Ready";
} else {
myLocation = asyncSnapshot.data
.toString()
.replaceAll("Latitude: ", "")
.replaceAll("Longitude: ", "");
List<String> options = [];
if (asyncSnapshot.connectionState == ConnectionState.done) {
options.addAll(asyncSnapshot.data!);
}
return displaySearchResults(userSearch, myLocation);
return Visibility(
visible: filterOn,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: width / 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: MihDropdownField(
controller: businessTypeController,
hintText: "Business Type Filter",
dropdownOptions: options,
requiredText: true,
editable: true,
enableSearch: true,
),
),
const SizedBox(width: 10),
MihButton(
onPressed: () {
if (businessTypeController.text.isNotEmpty) {
searchPressed();
} else {
MihAlertServices().errorAlert(
"Business Type Not Selected",
"Please ensure you have selected a Business Type before seareching for Businesses of Mzansi",
context,
);
}
},
buttonColor: MzansiInnovationHub.of(context)!
.theme
.successColor(),
elevation: 10,
child: Text(
"Search",
style: TextStyle(
color: MzansiInnovationHub.of(context)!
.theme
.primaryColor(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
}),
const SizedBox(height: 10),
displaySearchResults(userSearch, widget.myLocation ?? ""),
],
),
);

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:mzansi_innovation_hub/main.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/bookmarked_business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/business_review.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_alert.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart';
import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/components/mih_add_bookmark_alert.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/components/mih_delete_bookmark_alert.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/components/mih_review_business_window.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_mzansi_directory_services.dart';
import 'package:redacted/redacted.dart';
import 'package:supertokens_flutter/supertokens.dart';
import 'package:url_launcher/url_launcher.dart';
class MihBusinessCard extends StatefulWidget {
final Business business;
final String? startUpSearch;
// final String businessid;
// final String businessName;
// final String cellNumber;
// final String email;
// final String gpsLocation;
// final String? website;
// final double rating;
final double width;
const MihBusinessCard({
super.key,
required this.business,
required this.startUpSearch,
// required this.businessid,
// required this.businessName,
// required this.cellNumber,
// required this.email,
// required this.gpsLocation,
// required this.rating,
// this.website,
required this.width,
});
@@ -41,6 +29,16 @@ class MihBusinessCard extends StatefulWidget {
}
class _MihBusinessCardState extends State<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 {
final Uri url = Uri(scheme: 'tel', path: phoneNumber);
if (await canLaunchUrl(url)) {
@@ -269,6 +267,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
String subLabel,
IconData icon,
Color? iconColor,
bool redacted,
Function()? ontap,
) {
return Material(
@@ -288,16 +287,25 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
child: Row(
children: [
Container(
width: 45,
height: 45,
decoration: BoxDecoration(
color: iconColor,
borderRadius: BorderRadius.circular(15),
),
padding: const EdgeInsets.all(5.0),
child: Icon(
icon,
size: 35,
color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
child: FittedBox(
child: Icon(
icon,
// size: 35,
color:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
),
),
).redacted(
context: context,
redact: redacted,
configuration: getRedactedConfiguration(),
),
SizedBox(width: 20),
Expanded(
@@ -316,6 +324,10 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
.primaryColor(),
height: 1.0,
),
).redacted(
context: context,
redact: redacted,
configuration: getRedactedConfiguration(),
),
Text(
subLabel,
@@ -326,6 +338,10 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
.theme
.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
Widget build(BuildContext context) {
// double screenWidth = MediaQuery.of(context).size.width;
@@ -369,6 +406,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
"Give us a quick call.",
Icons.phone,
MihColors.getGreenColor(context),
false,
() {
// print("Calling ${widget.cellNumber}");
_makePhoneCall(widget.business.contact_no);
@@ -382,6 +420,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
"Send us an email.",
Icons.email,
MihColors.getPinkColor(context),
false,
() {
// print("Emailing ${widget.email}");
_launchEmail(
@@ -391,58 +430,182 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
);
},
),
Divider(
color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
Visibility(
visible: isValidGps(widget.business.gps_location),
child: Column(
children: [
Divider(
color:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
),
_buildContactInfo(
"Location",
"Come visit us.",
Icons.location_on,
MihColors.getOrangeColor(context),
false,
() {
final latitude = double.parse(
widget.business.gps_location.split(',')[0]);
final longitude = double.parse(
widget.business.gps_location.split(',')[1]);
_launchGoogleMapsWithUrl(
latitude: latitude,
longitude: longitude,
);
},
),
],
),
),
_buildContactInfo(
"Location",
"Come visit us.",
Icons.location_on,
MihColors.getOrangeColor(context),
() {
final latitude =
double.parse(widget.business.gps_location.split(',')[0]);
final longitude =
double.parse(widget.business.gps_location.split(',')[1]);
_launchGoogleMapsWithUrl(
latitude: latitude,
longitude: longitude,
);
Visibility(
visible: widget.business.website.isNotEmpty &&
widget.business.website != "",
child: Column(
children: [
Divider(
color:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
),
_buildContactInfo(
"Website",
"Find out more about us.",
Icons.vpn_lock,
MihColors.getRedColor(context),
false,
() {
_launchWebsite(widget.business.website);
},
),
],
),
),
FutureBuilder(
future: _businessReviewFuture,
builder: (context, asyncSnapshot) {
if (asyncSnapshot.connectionState == ConnectionState.waiting) {
// return const SizedBox.shrink();
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Divider(
color: MzansiInnovationHub.of(context)!
.theme
.primaryColor(),
),
),
Container(
child: _buildContactInfo(
"Loading Rating",
"Loading your rating.",
Icons.star_rate_rounded,
MihColors.getYellowColor(context),
true,
null,
),
).redacted(context: context, redact: true),
],
);
} else {
BusinessReview? businessReview = asyncSnapshot.data;
String ratingDisplayTitle = "";
if (businessReview == null) {
ratingDisplayTitle = "Rate Us";
} else {
ratingDisplayTitle = "Update Rating";
}
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Divider(
color: MzansiInnovationHub.of(context)!
.theme
.primaryColor(),
),
),
_buildContactInfo(
ratingDisplayTitle,
"Let us know how we are doing.",
Icons.star_rate_rounded,
MihColors.getYellowColor(context),
false,
() {
businessReviewRatingWindow(
businessReview, true, widget.width);
},
),
],
);
}
},
),
Visibility(
visible: widget.business.website.isNotEmpty &&
widget.business.website != "",
child: Divider(
color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
),
),
Visibility(
visible: widget.business.website.isNotEmpty &&
widget.business.website != "",
child: _buildContactInfo(
"Website",
"Find out more about us.",
Icons.vpn_lock,
MihColors.getRedColor(context),
() {
_launchWebsite(widget.business.website);
},
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Divider(
color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
),
),
_buildContactInfo(
"Rate Us",
"Let us know how we are doing.",
Icons.star_rate_rounded,
MihColors.getYellowColor(context),
() {
businessReviewRatingWindow(true, widget.width);
FutureBuilder(
future: _bookmarkedBusinessFuture,
builder: (context, asyncSnapshot) {
if (asyncSnapshot.connectionState == ConnectionState.waiting) {
// return const SizedBox.shrink();
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Divider(
color: MzansiInnovationHub.of(context)!
.theme
.primaryColor(),
),
),
Container(
child: _buildContactInfo(
"Loading Bookmark",
"Loading your bookmark.",
Icons.bookmark_add_rounded,
MihColors.getBluishPurpleColor(context),
true,
null,
),
),
],
);
} else {
BookmarkedBusiness? bookmarkBusiness = asyncSnapshot.data;
String bookmarkDisplayTitle = "";
if (bookmarkBusiness == null) {
bookmarkDisplayTitle = "Bookmark Us";
} else {
bookmarkDisplayTitle = "Remove Bookmark";
}
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Divider(
color: MzansiInnovationHub.of(context)!
.theme
.primaryColor(),
),
),
_buildContactInfo(
bookmarkDisplayTitle,
"Save us for later.",
bookmarkBusiness == null
? Icons.bookmark_add_rounded
: Icons.bookmark_remove_rounded,
MihColors.getBluishPurpleColor(context),
false,
() {
// _launchWebsite(widget.website);
if (bookmarkBusiness == null) {
showAddBookmarkAlert();
} else {
showDeleteBookmarkAlert(bookmarkBusiness);
}
},
),
],
);
}
},
),
// Padding(
@@ -455,7 +618,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
// "Bookmark",
// "Save us for later.",
// Icons.bookmark_add_rounded,
// MihColors.getBluishPurpleColor(context),
// MihColors.getBluishPurpleColor(context),
// () {
// // _launchWebsite(widget.website);
// print("Saving ${widget.business.Name} to Directory");
@@ -476,115 +639,34 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
}
Future<void> businessReviewRatingWindow(
bool previouslyRated, double width) async {
BusinessReview? myReview, bool previouslyRated, double width) async {
showDialog(
context: context,
builder: (context) => FutureBuilder(
future: getUserReview(),
builder: (context, asyncSnapshot) {
if (asyncSnapshot.connectionState == ConnectionState.waiting) {
return const Mihloadingcircle(
message: "Checking for previous reviews...",
);
} else if (asyncSnapshot.connectionState == ConnectionState.done) {
return MihReviewBusinessWindow(
business: widget.business,
businessReview: asyncSnapshot.data,
screenWidth: width,
readOnly: false,
startUpSearch: widget.startUpSearch,
);
} else {
return MihPackageAlert(
alertColour: MzansiInnovationHub.of(context)!.theme.errorColor(),
alertIcon: Icon(
Icons.warning_rounded,
size: 100,
color: MzansiInnovationHub.of(context)!.theme.errorColor(),
),
alertTitle: "Error Pulling Data",
alertBody: Column(
children: [
Text(
"Please ensure you are connectede top the internet and you are running the latest version of MIH then try again.",
style: TextStyle(
color: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
fontSize: 15,
),
),
],
),
);
}
},
builder: (context) => MihReviewBusinessWindow(
business: widget.business,
businessReview: myReview,
screenWidth: width,
readOnly: false,
),
);
}
void showBookmarkAlert() {
void showAddBookmarkAlert() {
showDialog(
context: context,
builder: (context) => MihPackageAlert(
alertColour: MihColors.getSecondaryColor(context),
alertIcon: Icon(
Icons.warning_rounded,
size: 100,
color: MihColors.getSecondaryColor(context),
),
alertTitle: "Bookmark Business",
alertBody: Column(
children: [
Text(
"Are you sure you want to save ${widget.business.Name} to your Mzansi Directory?",
style: TextStyle(
color: MzansiInnovationHub.of(context)!.theme.secondaryColor(),
fontSize: 15,
),
),
const SizedBox(height: 25),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
MihButton(
width: 300,
onPressed: () async {
Navigator.of(context).pop();
},
buttonColor:
MzansiInnovationHub.of(context)!.theme.errorColor(),
child: Text(
"Cancel",
style: TextStyle(
color:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
MihButton(
width: 300,
onPressed: () {},
buttonColor:
MzansiInnovationHub.of(context)!.theme.successColor(),
child: Text(
"Save Business",
style: TextStyle(
color:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
builder: (context) => MihAddBookmarkAlert(
business: widget.business,
),
);
}
void showDeleteBookmarkAlert(BookmarkedBusiness? bookmarkBusiness) {
showDialog(
context: context,
builder: (context) => MihDeleteBookmarkAlert(
business: widget.business,
bookmarkBusiness: bookmarkBusiness,
startUpSearch: widget.startUpSearch,
));
}
}

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

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

View File

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

View File

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

View File

@@ -161,6 +161,7 @@ class _MihCardsState extends State<MihCards> {
"Spar",
"Spur",
"TFG Group",
"Total Energies",
"Toys R Us",
"Woermann Brock",
"Woolworths"
@@ -367,7 +368,7 @@ class _MihCardsState extends State<MihCards> {
builder: (BuildContext context,
List<MIHLoyaltyCard> value, Widget? child) {
return BuildLoyaltyCardList(
cardList: searchShopName.value,
cardList: value,
signedInUser: widget.signedInUser,
navIndex: 0,
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;
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(
String searchText,
String searchType,
BuildContext context,
) async {
String newSearchText = "All";
if (searchText.isNotEmpty) {
newSearchText = searchText;
}
String newSearchType = "All";
if (searchType.isNotEmpty) {
newSearchType = searchType;
}
if (searchText.isEmpty && searchType.isEmpty) {
return [];
}
var response = await http.get(
Uri.parse("${AppEnviroment.baseApiUrl}/businesses/search/$searchText"),
Uri.parse(
"${AppEnviroment.baseApiUrl}/business/search/$newSearchType/$newSearchText"),
headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8"
},
@@ -29,7 +59,7 @@ class MihBusinessDetailsServices {
}
}
Future<Business?> getBusinessDetails(
Future<Business?> getBusinessDetailsByUser(
String app_id,
) async {
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(
String appId,
String busineName,

View File

@@ -1,4 +1,5 @@
import 'dart:convert';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/bookmarked_business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/business_review.dart';
import 'package:mzansi_innovation_hub/mih_config/mih_env.dart';
import 'package:supertokens_flutter/http.dart' as http;
@@ -6,6 +7,10 @@ import 'package:supertokens_flutter/http.dart' as http;
class MihMzansiDirectoryServices {
final baseAPI = AppEnviroment.baseApiUrl;
//########################################################
//# Business Ratings #
//########################################################
Future<BusinessReview?> getUserReviewOfBusiness(
String app_id,
String business_id,
@@ -33,10 +38,9 @@ class MihMzansiDirectoryServices {
List<BusinessReview> businessReviews = List<BusinessReview>.from(
l.map((model) => BusinessReview.fromJson(model)));
return businessReviews;
} else if (response.statusCode == 404){
} else if (response.statusCode == 404) {
return [];
}
else {
} else {
throw Exception('failed to fetch Business Reviews');
}
}
@@ -128,4 +132,84 @@ class MihMzansiDirectoryServices {
return response.statusCode;
}
}
//########################################################
//# Bookmarked Business #
//########################################################
Future<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
Business? business = await MihBusinessDetailsServices().getBusinessDetails(
Business? business =
await MihBusinessDetailsServices().getBusinessDetailsByUser(
uid,
);
if (business != null) {

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ from sqlalchemy.orm import Session
from pydantic import BaseModel
from datetime import datetime
import mih_database.mihDbConnections
from mih_database.mihDbObjects import User, Business, BusinessRating
from mih_database.mihDbObjects import User, Business, BusinessRating, BookmarkedBusiness
from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.session import SessionContainer
from fastapi import Depends
@@ -40,6 +40,17 @@ class BusinessRatingUpdateRequest(BaseModel):
rating_old_score: str
current_rating: str
class BookmarkedBusinessInsertRequest(BaseModel):
app_id: str
business_id: str
class BookmarkedBusinessDeleteRequest(BaseModel):
idbookmarked_businesses: int
########################################################
# Business Ratings #
########################################################
@router.get("/mzansi-directory/business-ratings/user/{app_id}/{business_id}", tags=["Mzansi Directory"])
async def read_all_ratings_by_business_id(app_id: str,business_id: str, session: SessionContainer = Depends(verify_session())): # , session: SessionContainer = Depends(verify_session())
dbEngine = mih_database.mihDbConnections.dbAllConnect()
@@ -295,4 +306,154 @@ async def UpdatePatient(itemRequest : BusinessRatingUpdateRequest, session: Sess
)
finally:
dbSession.close()
return {"message": "Successfully wUpdated Record"}
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"}