Merge pull request #235 from yaso-meth/NEW--Mzansi-Directory-Ratings

NEW--Mzansi-Directory-Ratings
This commit is contained in:
yaso-meth
2025-07-23 12:22:53 +02:00
committed by GitHub
28 changed files with 1546 additions and 142 deletions

View File

@@ -31,6 +31,16 @@ class BusinessArguments {
); );
} }
class BusinessViewArguments {
final Business business;
final String? startUpSearch;
BusinessViewArguments(
this.business,
this.startUpSearch,
);
}
class HomeArguments { class HomeArguments {
final AppUser signedInUser; final AppUser signedInUser;
final BusinessUser? businessUser; final BusinessUser? businessUser;
@@ -218,6 +228,16 @@ class MzansiAiArguments {
); );
} }
class MzansiDirectoryArguments {
final String? startUpSearch;
final bool personalSearch;
MzansiDirectoryArguments(
this.startUpSearch,
this.personalSearch,
);
}
class TestArguments { class TestArguments {
final AppUser user; final AppUser user;
final Business? business; final Business? business;

View File

@@ -0,0 +1,46 @@
class BusinessReview {
final int idbusiness_ratings;
final String app_id;
final String business_id;
final String rating_title;
final String rating_description;
final String rating_score;
final String date_time;
final String reviewer;
BusinessReview({
required this.idbusiness_ratings,
required this.app_id,
required this.business_id,
required this.rating_title,
required this.rating_description,
required this.rating_score,
required this.date_time,
required this.reviewer,
});
factory BusinessReview.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
"idbusiness_ratings": int idbusiness_ratings,
"app_id": String app_id,
"business_id": String business_id,
"rating_title": String rating_title,
"rating_description": String rating_description,
"rating_score": String rating_score,
"date_time": String date_time,
"reviewer": String reviewer,
} =>
BusinessReview(
idbusiness_ratings: idbusiness_ratings,
app_id: app_id,
business_id: business_id,
rating_title: rating_title,
rating_description: rating_description,
rating_score: rating_score,
date_time: date_time,
reviewer: reviewer,
),
_ => throw const FormatException('Failed to load loyalty card objects'),
};
}
}

View File

@@ -9,7 +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_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';
import 'package:mzansi_innovation_hub/mih_services/mih_validation_services.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_validation_services.dart';
@@ -291,14 +291,16 @@ class _PackageToolOneState extends State<PackageToolOne> {
} }
}), }),
const SizedBox(height: 10), const SizedBox(height: 10),
MihBusinessCard( // MihBusinessCard(
businessName: "Mzansi Innovation Hub", // businessid: "123456",
cellNumber: "0788300006", // businessName: "Mzansi Innovation Hub",
email: "yasien.meth@mzansi-innovation-hub.co.za", // cellNumber: "0788300006",
gpsLocation: "-26.1853611, 28.134664", // email: "yasien.meth@mzansi-innovation-hub.co.za",
website: // gpsLocation: "-26.1853611, 28.134664",
"https://app.mzansi-innovation-hub.co.za/privacy.html", // website:
), // "https://app.mzansi-innovation-hub.co.za/privacy.html",
// rating: 3.25,
// ),
const SizedBox(height: 10), const SizedBox(height: 10),
Divider( Divider(
color: color:

View File

@@ -1,3 +1,4 @@
import 'package:custom_rating_bar/custom_rating_bar.dart';
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_package_components/mih_single_child_scroll.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_single_child_scroll.dart';
@@ -36,6 +37,14 @@ class _PackageToolTwoState extends State<PackageToolTwo> {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
RatingBar(
filledIcon: Icons.star,
emptyIcon: Icons.star_border,
onRatingChanged: (value) => debugPrint('$value'),
initialRating: 3,
maxRating: 5,
),
const SizedBox(height: 10),
Container( Container(
color: Colors.black, color: Colors.black,
width: 200, width: 200,

View File

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_print_prevew.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_print_prevew.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/arguments.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/Example/package_test.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/Example/package_test.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_notification_message.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_notification_message.dart';
import 'package:mzansi_innovation_hub/mih_packages/about_mih/about_mih.dart'; import 'package:mzansi_innovation_hub/mih_packages/about_mih/about_mih.dart';
@@ -122,13 +121,13 @@ class RouteGenerator {
// } // }
// break; // Use break and fall through to _errorRoute if argument type is wrong // break; // Use break and fall through to _errorRoute if argument type is wrong
case AppRoutes.mzansiDirectory: case AppRoutes.mzansiDirectory:
// if (args is AuthArguments) { if (args is MzansiDirectoryArguments) {
return MaterialPageRoute( return MaterialPageRoute(
settings: settings, settings: settings,
builder: (_) => MzansiDirectory(), builder: (_) => MzansiDirectory(arguments: args),
); );
// } }
// break; break;
case AppRoutes.notifications: case AppRoutes.notifications:
if (args is NotificationArguments) { if (args is NotificationArguments) {
return MaterialPageRoute( return MaterialPageRoute(
@@ -188,10 +187,10 @@ class RouteGenerator {
break; break;
case AppRoutes.businessProfileView: case AppRoutes.businessProfileView:
if (args is Business) { if (args is BusinessViewArguments) {
return MaterialPageRoute( return MaterialPageRoute(
settings: settings, settings: settings,
builder: (_) => MzansiBusinessProfileView(business: args), builder: (_) => MzansiBusinessProfileView(arguments: args),
); );
} }
break; break;

View File

@@ -394,48 +394,51 @@ class _MihSignInState extends State<MihSignIn> {
//spacer //spacer
const SizedBox(height: 35), const SizedBox(height: 35),
Center( Visibility(
child: SizedBox( visible: AppEnviroment.getEnv() == "Dev",
width: width, child: Center(
//height: 100.0, child: SizedBox(
child: Row( width: width,
mainAxisAlignment: MainAxisAlignment.center, //height: 100.0,
children: [ child: Row(
const Flexible( mainAxisAlignment: MainAxisAlignment.center,
flex: 1, children: [
child: Padding( const Flexible(
padding: EdgeInsets.only(right: 10.0), flex: 1,
child: Divider(), child: Padding(
), padding: EdgeInsets.only(right: 10.0),
), child: Divider(),
Flexible(
flex: 1,
child: GestureDetector(
child: Text(
'Use Sandox Profile',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: MzansiInnovationHub.of(context)!
.theme
.secondaryColor()),
), ),
onTap: () {
setState(() {
showProfiles = !showProfiles;
});
},
), ),
), Flexible(
const Flexible( flex: 1,
flex: 1, child: GestureDetector(
child: Padding( child: Text(
padding: EdgeInsets.only(left: 10.0), 'Use Sandox Profile',
child: Divider(), textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: MzansiInnovationHub.of(context)!
.theme
.secondaryColor()),
),
onTap: () {
setState(() {
showProfiles = !showProfiles;
});
},
),
), ),
), const Flexible(
], flex: 1,
child: Padding(
padding: EdgeInsets.only(left: 10.0),
child: Divider(),
),
),
],
),
), ),
), ),
), ),

View File

@@ -1,15 +1,18 @@
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/arguments.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_business_profile_preview.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_business_profile_preview.dart';
class BuildBusinessSearchResultsList extends StatefulWidget { class BuildBusinessSearchResultsList extends StatefulWidget {
final List<Business> businessList; final List<Business> businessList;
final String myLocation; final String myLocation;
final String? startUpSearch;
const BuildBusinessSearchResultsList({ const BuildBusinessSearchResultsList({
super.key, super.key,
required this.businessList, required this.businessList,
required this.myLocation, required this.myLocation,
required this.startUpSearch,
}); });
@override @override
@@ -37,7 +40,10 @@ class _BuildBusinessSearchResultsListState
onTap: () { onTap: () {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
'/business-profile/view', '/business-profile/view',
arguments: widget.businessList[index], arguments: BusinessViewArguments(
widget.businessList[index],
widget.businessList[index].Name,
),
); );
}, },
splashColor: MzansiInnovationHub.of(context)! splashColor: MzansiInnovationHub.of(context)!

View File

@@ -1,11 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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_search_mzansi.dart'; import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/package_tools/mih_search_mzansi.dart';
class MzansiDirectory extends StatefulWidget { class MzansiDirectory extends StatefulWidget {
const MzansiDirectory({super.key}); final MzansiDirectoryArguments arguments;
const MzansiDirectory({
super.key,
required this.arguments,
});
@override @override
State<MzansiDirectory> createState() => _MzansiDirectoryState(); State<MzansiDirectory> createState() => _MzansiDirectoryState();
@@ -32,7 +37,10 @@ class _MzansiDirectoryState extends State<MzansiDirectory> {
List<Widget> getToolBody() { List<Widget> getToolBody() {
List<Widget> toolBodies = [ List<Widget> toolBodies = [
MihSearchMzansi(), MihSearchMzansi(
startUpSearch: widget.arguments.startUpSearch,
personalSearch: widget.arguments.personalSearch,
),
// MihContacts(), // MihContacts(),
// MihFavouriteBusinesses(), // MihFavouriteBusinesses(),
]; ];

View File

@@ -1,5 +1,6 @@
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/arguments.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_tile.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tile.dart';
@@ -21,7 +22,10 @@ class _MzansiDirectoryTileState extends State<MzansiDirectoryTile> {
onTap: () { onTap: () {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
'/mzansi-directory', '/mzansi-directory',
// arguments: WalletArguments(widget.signedInUser, 0), arguments: MzansiDirectoryArguments(
null, // startUpSearch
true, // personalSearch
),
); );
}, },
appName: "Mzansi Directory", appName: "Mzansi Directory",

View File

@@ -15,7 +15,13 @@ 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 {
const MihSearchMzansi({super.key}); final String? startUpSearch;
final bool personalSearch;
const MihSearchMzansi({
super.key,
required this.startUpSearch,
required this.personalSearch,
});
@override @override
State<MihSearchMzansi> createState() => _MihSearchMzansiState(); State<MihSearchMzansi> createState() => _MihSearchMzansiState();
@@ -24,7 +30,7 @@ class MihSearchMzansi extends StatefulWidget {
class _MihSearchMzansiState extends State<MihSearchMzansi> { class _MihSearchMzansiState extends State<MihSearchMzansi> {
final TextEditingController mzansiSearchController = TextEditingController(); final TextEditingController mzansiSearchController = TextEditingController();
final FocusNode searchFocusNode = FocusNode(); final FocusNode searchFocusNode = FocusNode();
bool userSearch = true; 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 = late Future<Position?> futurePosition =
@@ -41,6 +47,17 @@ class _MihSearchMzansiState extends State<MihSearchMzansi> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
setState(() {
userSearch = widget.personalSearch;
mzansiSearchController.text = widget.startUpSearch ?? "";
if (userSearch) {
futureUserSearchResults =
MihUserServices().searchUsers(mzansiSearchController.text, context);
} else {
futureBusinessSearchResults = MihBusinessDetailsServices()
.searchBusinesses(mzansiSearchController.text, context);
}
});
} }
@override @override
@@ -82,7 +99,7 @@ class _MihSearchMzansiState extends State<MihSearchMzansi> {
}); });
}, },
icon: Icon( icon: Icon(
Icons.display_settings, Icons.swap_horiz_rounded,
size: 35, size: 35,
color: color:
MzansiInnovationHub.of(context)!.theme.primaryColor(), MzansiInnovationHub.of(context)!.theme.primaryColor(),
@@ -246,6 +263,7 @@ class _MihSearchMzansiState extends State<MihSearchMzansi> {
BuildBusinessSearchResultsList( BuildBusinessSearchResultsList(
businessList: snapshot.requireData!, businessList: snapshot.requireData!,
myLocation: myLocation, myLocation: myLocation,
startUpSearch: mzansiSearchController.text,
), ),
], ],
); );

View File

@@ -1,21 +1,38 @@
import 'package:custom_rating_bar/custom_rating_bar.dart';
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/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_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_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: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 String businessName; final Business business;
final String cellNumber; final String? startUpSearch;
final String email; // final String businessid;
final String gpsLocation; // final String businessName;
final String? website; // final String cellNumber;
// final String email;
// final String gpsLocation;
// final String? website;
// final double rating;
final double width;
const MihBusinessCard({ const MihBusinessCard({
super.key, super.key,
required this.businessName, required this.business,
required this.cellNumber, required this.startUpSearch,
required this.email, // required this.businessid,
required this.gpsLocation, // required this.businessName,
this.website, // required this.cellNumber,
// required this.email,
// required this.gpsLocation,
// required this.rating,
// this.website,
required this.width,
}); });
@override @override
@@ -41,7 +58,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
alertBody: Column( alertBody: Column(
children: [ children: [
Text( Text(
"We couldn't open your phone app to call ${widget.cellNumber}. To fix this, make sure you have a phone application installed and it's set as your default dialer.", "We couldn't open your phone app to call ${widget.business.contact_no}. To fix this, make sure you have a phone application installed and it's set as your default dialer.",
style: TextStyle( style: TextStyle(
color: MzansiInnovationHub.of(context)! color: MzansiInnovationHub.of(context)!
.theme .theme
@@ -91,7 +108,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
alertBody: Column( alertBody: Column(
children: [ children: [
Text( Text(
"We couldn't launch your email app to send a message to ${widget.email}. To fix this, please confirm that you have an email application installed and that it's set as your default.", "We couldn't launch your email app to send a message to ${widget.business.bus_email}. To fix this, please confirm that you have an email application installed and that it's set as your default.",
style: TextStyle( style: TextStyle(
color: MzansiInnovationHub.of(context)! color: MzansiInnovationHub.of(context)!
.theme .theme
@@ -132,7 +149,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
alertBody: Column( alertBody: Column(
children: [ children: [
Text( Text(
"There was an issue opening maps for ${widget.businessName}. This usually happens if you don't have a maps app installed or it's not set as your default. Please install one to proceed.", "There was an issue opening maps for ${widget.business.Name}. This usually happens if you don't have a maps app installed or it's not set as your default. Please install one to proceed.",
style: TextStyle( style: TextStyle(
color: MzansiInnovationHub.of(context)! color: MzansiInnovationHub.of(context)!
.theme .theme
@@ -161,7 +178,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
alertBody: Column( alertBody: Column(
children: [ children: [
Text( Text(
"There was an issue opening maps for ${widget.businessName}. This usually happens if you don't have a maps app installed or it's not set as your default. Please install one to proceed.", "There was an issue opening maps for ${widget.business.Name}. This usually happens if you don't have a maps app installed or it's not set as your default. Please install one to proceed.",
style: TextStyle( style: TextStyle(
color: MzansiInnovationHub.of(context)! color: MzansiInnovationHub.of(context)!
.theme .theme
@@ -319,8 +336,17 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
); );
} }
Future<BusinessReview?> getUserReview() async {
String user_id = await SuperTokens.getUserId();
return await MihMzansiDirectoryServices().getUserReviewOfBusiness(
user_id,
widget.business.business_id,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// double screenWidth = MediaQuery.of(context).size.width;
return Material( return Material(
color: MzansiInnovationHub.of(context)! color: MzansiInnovationHub.of(context)!
.theme .theme
@@ -336,6 +362,36 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
), ),
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 10),
RatingBar.readOnly(
size: 50,
alignment: Alignment.center,
filledIcon: Icons.star,
emptyIcon: Icons.star_border,
halfFilledIcon: Icons.star_half,
filledColor: const Color(0xffe9e8a1),
// MzansiInnovationHub.of(context)!.theme.primaryColor(),
emptyColor: MzansiInnovationHub.of(context)!.theme.primaryColor(),
halfFilledColor: const Color(0xffe9e8a1),
// MzansiInnovationHub.of(context)!.theme.primaryColor(),
isHalfAllowed: true,
initialRating: widget.business.rating.isNotEmpty
? double.parse(widget.business.rating)
: 0,
maxRating: 5,
),
// Text(
// "Rating: ${widget.rating}",
// style: TextStyle(
// fontSize: 15,
// fontWeight: FontWeight.bold,
// color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
// height: 1.0,
// ),
// ),
// Divider(
// color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
// ),
const SizedBox(height: 10), const SizedBox(height: 10),
_buildContactInfo( _buildContactInfo(
"Call", "Call",
@@ -344,7 +400,7 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
const Color(0xffaff0b3), const Color(0xffaff0b3),
() { () {
// print("Calling ${widget.cellNumber}"); // print("Calling ${widget.cellNumber}");
_makePhoneCall(widget.cellNumber); _makePhoneCall(widget.business.contact_no);
}, },
), ),
Divider( Divider(
@@ -358,9 +414,9 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
() { () {
// print("Emailing ${widget.email}"); // print("Emailing ${widget.email}");
_launchEmail( _launchEmail(
widget.email, widget.business.bus_email,
"Inquiery about ${widget.businessName}", "Inquiery about ${widget.business.Name}",
"Dear ${widget.businessName},\n\nI would like to inquire about your services.\n\nBest regards,\n", "Dear ${widget.business.Name},\n\nI would like to inquire about your services.\n\nBest regards,\n",
); );
}, },
), ),
@@ -371,11 +427,12 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
"Location", "Location",
"Come visit us.", "Come visit us.",
Icons.location_on, Icons.location_on,
const Color(0xffe9e8a1), const Color(0xffd69d7d),
() { () {
final latitude = double.parse(widget.gpsLocation.split(',')[0]); final latitude =
double.parse(widget.business.gps_location.split(',')[0]);
final longitude = final longitude =
double.parse(widget.gpsLocation.split(',')[1]); double.parse(widget.business.gps_location.split(',')[1]);
_launchGoogleMapsWithUrl( _launchGoogleMapsWithUrl(
latitude: latitude, latitude: latitude,
longitude: longitude, longitude: longitude,
@@ -383,39 +440,40 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
}, },
), ),
Visibility( Visibility(
visible: widget.website != null && widget.website! != "", visible: widget.business.website.isNotEmpty &&
widget.business.website != "",
child: Divider( child: Divider(
color: MzansiInnovationHub.of(context)!.theme.primaryColor(), color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
), ),
), ),
Visibility( Visibility(
visible: widget.website != null && widget.website! != "", visible: widget.business.website.isNotEmpty &&
widget.business.website != "",
child: _buildContactInfo( child: _buildContactInfo(
"Website", "Website",
"Find out more about us.", "Find out more about us.",
Icons.vpn_lock, Icons.vpn_lock,
const Color(0xffd67d8a), const Color(0xffd67d8a),
() { () {
_launchWebsite(widget.website!); _launchWebsite(widget.business.website);
}, },
), ),
), ),
// Padding( Padding(
// padding: const EdgeInsets.symmetric(horizontal: 10.0), padding: const EdgeInsets.symmetric(horizontal: 10.0),
// child: Divider( child: Divider(
// color: MzansiInnovationHub.of(context)!.theme.primaryColor(), color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
// ), ),
// ), ),
// _buildContactInfo( _buildContactInfo(
// "Rate Us", "Rate Us",
// "Let us know how we are doing.", "Let us know how we are doing.",
// Icons.star_rate_rounded, Icons.star_rate_rounded,
// const Color(0xffd69d7d), const Color(0xffe9e8a1),
// () { () {
// print("Opeining rating dialog"); businessReviewRatingWindow(true, widget.width);
// // _launchWebsite(widget.website); },
// }, ),
// ),
// Padding( // Padding(
// padding: const EdgeInsets.symmetric(horizontal: 10.0), // padding: const EdgeInsets.symmetric(horizontal: 10.0),
// child: Divider( // child: Divider(
@@ -444,4 +502,52 @@ class _MihBusinessCardState extends State<MihBusinessCard> {
), ),
); );
} }
Future<void> businessReviewRatingWindow(
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,
),
),
],
),
);
}
},
),
);
}
} }

View File

@@ -0,0 +1,537 @@
import 'package:custom_rating_bar/custom_rating_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.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_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_form.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_window.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_text_form_field.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.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:mzansi_innovation_hub/mih_services/mih_validation_services.dart';
import 'package:supertokens_flutter/supertokens.dart';
class MihReviewBusinessWindow extends StatefulWidget {
final Business business;
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
State<MihReviewBusinessWindow> createState() =>
_MihReviewBusinessWindowState();
}
class _MihReviewBusinessWindowState extends State<MihReviewBusinessWindow> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _reviewTitleController = TextEditingController();
final TextEditingController _reviewScoreController = TextEditingController();
final TextEditingController _reviewReviewerController =
TextEditingController();
final TextEditingController _reviewDescriptionController =
TextEditingController();
late final VoidCallback _reviewDescriptionListener;
final ValueNotifier<int> _counter = ValueNotifier<int>(0);
String userId = "";
void showDeleteReviewAlert() {
showDialog(
context: context,
builder: (context) => MihPackageAlert(
alertColour: MzansiInnovationHub.of(context)!.theme.errorColor(),
alertIcon: Icon(
Icons.warning_rounded,
size: 100,
color: MzansiInnovationHub.of(context)!.theme.errorColor(),
),
alertTitle: "Delete Review",
alertBody: Column(
children: [
Text(
"Are you sure you want to delete this review? This action cannot be undone.",
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 {
showDialog(
context: context,
builder: (context) {
return const Mihloadingcircle();
},
);
await MihMzansiDirectoryServices()
.deleteBusinessReview(
widget.businessReview!.idbusiness_ratings,
widget.businessReview!.business_id,
widget.businessReview!.rating_score,
widget.business.rating,
)
.then((statusCode) {
Navigator.of(context).pop(); //Remove loading dialog
Navigator.of(context).pop(); //Remove delete dialog
if (statusCode == 200) {
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(
widget.startUpSearch, // startUpSearch
false, // personalSearch
),
);
MihAlertServices().successAlert(
"Successfully Deleted Review!",
"Your review has successfully been delete and will no longer appear under the business.",
context,
);
} else {
MihAlertServices().errorAlert(
"Error Deleting Review",
"There was an error deleting your review. Please try again later.",
context,
);
}
});
},
buttonColor:
MzansiInnovationHub.of(context)!.theme.errorColor(),
child: Text(
"Delete",
style: TextStyle(
color:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
MihButton(
width: 300,
onPressed: () {
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,
),
),
),
],
),
],
),
),
);
}
Color getMissionVisionLimitColor(int limit) {
if (_counter.value <= limit) {
return MzansiInnovationHub.of(context)!.theme.secondaryColor();
} else {
return MzansiInnovationHub.of(context)!.theme.errorColor();
}
}
void submitForm() async {
showDialog(
context: context,
builder: (context) {
return const Mihloadingcircle();
},
);
if (widget.businessReview != null) {
await MihMzansiDirectoryServices()
.updateBusinessReview(
widget.businessReview!.idbusiness_ratings,
widget.businessReview!.business_id,
_reviewTitleController.text,
_reviewDescriptionController.text,
_reviewScoreController.text,
widget.businessReview!.rating_score,
widget.business.rating,
)
.then((statusCode) {
Navigator.of(context).pop(); //Remove loading dialog
if (statusCode == 200) {
Navigator.of(context).pop(); //pop window
Navigator.of(context).pop(); //pop business profile
Navigator.of(context).pop(); //pop directory
Navigator.of(context).pushNamed(
'/mzansi-directory',
arguments: MzansiDirectoryArguments(
widget.startUpSearch, // startUpSearch
false, // personalSearch
),
);
MihAlertServices().successAlert(
"Successfully Updated Review!",
"Your review has successfully been updated and will now appear under the business.",
context,
);
} else {
MihAlertServices().errorAlert(
"Error Updating Review",
"There was an error updating your review. Please try again later.",
context,
);
}
});
} else {
await MihMzansiDirectoryServices()
.addBusinessReview(
userId,
widget.business.business_id,
_reviewTitleController.text,
_reviewDescriptionController.text,
_reviewScoreController.text,
widget.business.rating.isEmpty ? "0.0" : widget.business.rating,
)
.then((statusCode) {
Navigator.of(context).pop(); //Remove loading dialog
if (statusCode == 201) {
Navigator.of(context).pop(); // pop window
Navigator.of(context).pop(); // pop business profile
Navigator.of(context).pop(); // pop directory
Navigator.of(context).pushNamed(
'/mzansi-directory',
arguments: MzansiDirectoryArguments(
widget.startUpSearch, // startUpSearch
false, // personalSearch
),
);
MihAlertServices().successAlert(
"Successfully Added Review!",
"Your review has successfully been added and will now appear under the business.",
context,
);
} else {
MihAlertServices().errorAlert(
"Error Adding Review",
"There was an error adding your review. Please try again later.",
context,
);
}
});
}
}
String getWindowTitle() {
if (widget.readOnly) {
return "Review Details";
} else if (widget.businessReview != null) {
return "Update Review";
} else {
return "Add Review";
}
}
@override
void dispose() {
super.dispose();
_reviewDescriptionController.removeListener(_reviewDescriptionListener);
}
@override
void initState() {
super.initState();
_reviewDescriptionListener = () {
setState(() {
_counter.value = _reviewDescriptionController.text.characters.length;
});
};
_reviewDescriptionController.addListener(_reviewDescriptionListener);
if (widget.businessReview != null) {
setState(() {
_reviewTitleController.text = widget.businessReview!.rating_title;
_reviewDescriptionController.text =
widget.businessReview!.rating_description;
_reviewScoreController.text = widget.businessReview!.rating_score;
_reviewReviewerController.text = widget.businessReview!.reviewer;
});
} else {
_reviewScoreController.text = "1.0"; // Default score
}
SuperTokens.getUserId().then((value) {
setState(() {
userId = value;
});
});
}
@override
Widget build(BuildContext context) {
// return const Placeholder();
return MihPackageWindow(
fullscreen: false,
windowTitle: getWindowTitle(),
onWindowTapClose: () {
Navigator.of(context).pop();
},
menuOptions: widget.businessReview != null && !widget.readOnly
? [
SpeedDialChild(
child: Icon(
Icons.delete,
color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
),
label: "Delete Review",
labelBackgroundColor:
MzansiInnovationHub.of(context)!.theme.successColor(),
labelStyle: TextStyle(
color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
fontWeight: FontWeight.bold,
),
backgroundColor:
MzansiInnovationHub.of(context)!.theme.successColor(),
onTap: () {
showDeleteReviewAlert();
},
),
]
: null,
windowBody: MihSingleChildScroll(
child: Padding(
padding:
MzansiInnovationHub.of(context)!.theme.screenType == "desktop"
? EdgeInsets.symmetric(horizontal: widget.screenWidth * 0.05)
: EdgeInsets.symmetric(horizontal: widget.screenWidth * 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
MihForm(
formKey: _formKey,
formFields: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Business Rating",
textAlign: TextAlign.left,
style: TextStyle(
color: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 4),
widget.readOnly
? RatingBar.readOnly(
size: 50,
alignment: Alignment.centerLeft,
filledIcon: Icons.star,
emptyIcon: Icons.star_border,
halfFilledIcon: Icons.star_half,
filledColor: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
emptyColor: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
halfFilledColor: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
isHalfAllowed: true,
initialRating: widget.businessReview != null
? double.parse(_reviewScoreController.text)
: 1,
maxRating: 5,
)
: RatingBar(
size: 50,
alignment: Alignment.centerLeft,
filledIcon: Icons.star,
emptyIcon: Icons.star_border,
halfFilledIcon: Icons.star_half,
filledColor: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
emptyColor: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
halfFilledColor: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
isHalfAllowed: true,
initialRating: widget.businessReview != null
? double.parse(_reviewScoreController.text)
: 1,
maxRating: 5,
onRatingChanged: (double) {
setState(() {
_reviewScoreController.text =
double.toStringAsFixed(1);
});
print(_reviewScoreController.text);
},
),
Visibility(
visible: widget.readOnly,
child: const SizedBox(height: 10),
),
Visibility(
visible: widget.readOnly,
child: MihTextFormField(
// width: 200,
fillColor: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
inputColor:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
controller: _reviewReviewerController,
multiLineInput: false,
requiredText: true,
readOnly: true,
hintText: "Reviewer",
validator: (value) {
return null;
},
),
),
const SizedBox(height: 10),
MihTextFormField(
// width: 200,
fillColor:
MzansiInnovationHub.of(context)!.theme.secondaryColor(),
inputColor:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
controller: _reviewTitleController,
multiLineInput: false,
requiredText: true,
readOnly: widget.readOnly,
hintText: "Review Title",
validator: (value) {
return MihValidationServices()
.isEmpty(_reviewTitleController.text);
},
),
const SizedBox(height: 10),
MihTextFormField(
height: 250,
fillColor:
MzansiInnovationHub.of(context)!.theme.secondaryColor(),
inputColor:
MzansiInnovationHub.of(context)!.theme.primaryColor(),
controller: _reviewDescriptionController,
multiLineInput: true,
requiredText: widget.readOnly,
readOnly: widget.readOnly,
hintText: "Review Description",
validator: (value) {
if (_reviewDescriptionController.text.isEmpty) {
return null;
} else {
return MihValidationServices().validateLength(
_reviewDescriptionController.text, 256);
}
},
),
Visibility(
visible: !widget.readOnly,
child: SizedBox(
height: 15,
child: ValueListenableBuilder(
valueListenable: _counter,
builder:
(BuildContext context, int value, Widget? child) {
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
"$value",
style: TextStyle(
color: getMissionVisionLimitColor(256),
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 5),
Text(
"/256",
style: TextStyle(
color: getMissionVisionLimitColor(256),
fontWeight: FontWeight.bold,
),
),
],
);
},
),
),
),
const SizedBox(height: 25),
Visibility(
visible: !widget.readOnly,
child: Center(
child: MihButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
submitForm();
} else {
MihAlertServices().formNotFilledCompletely(context);
}
},
buttonColor: MzansiInnovationHub.of(context)!
.theme
.successColor(),
width: 300,
child: Text(
widget.businessReview != null
? "Update Review"
: "Add Review",
style: TextStyle(
color: MzansiInnovationHub.of(context)!
.theme
.primaryColor(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
),
),
],
),
],
),
),
),
);
}
}

View File

@@ -1,15 +1,16 @@
import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.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:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_details_view.dart'; import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_details_view.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_reviews.dart';
class MzansiBusinessProfileView extends StatefulWidget { class MzansiBusinessProfileView extends StatefulWidget {
final Business business; final BusinessViewArguments arguments;
const MzansiBusinessProfileView({ const MzansiBusinessProfileView({
super.key, super.key,
required this.business, required this.arguments,
}); });
@override @override
@@ -54,6 +55,11 @@ class _MzansiBusinessProfileViewState extends State<MzansiBusinessProfileView> {
_selcetedIndex = 0; _selcetedIndex = 0;
}); });
}; };
temp[const Icon(Icons.star_rate_rounded)] = () {
setState(() {
_selcetedIndex = 1;
});
};
return MihPackageTools( return MihPackageTools(
tools: temp, tools: temp,
selcetedIndex: _selcetedIndex, selcetedIndex: _selcetedIndex,
@@ -62,7 +68,11 @@ class _MzansiBusinessProfileViewState extends State<MzansiBusinessProfileView> {
List<Widget> getToolBody() { List<Widget> getToolBody() {
List<Widget> toolBodies = [ List<Widget> toolBodies = [
MihBusinessDetailsView(business: widget.business), MihBusinessDetailsView(
business: widget.arguments.business,
startUpSearch: widget.arguments.startUpSearch,
),
MihBusinessReviews(business: widget.arguments.business),
]; ];
return toolBodies; return toolBodies;
} }
@@ -70,6 +80,7 @@ class _MzansiBusinessProfileViewState extends State<MzansiBusinessProfileView> {
List<String> getToolTitle() { List<String> getToolTitle() {
List<String> toolTitles = [ List<String> toolTitles = [
"Profile", "Profile",
"Reviews",
]; ];
return toolTitles; return toolTitles;
} }

View File

@@ -680,12 +680,18 @@ class _MihBusinessDetailsState extends State<MihBusinessDetails> {
SizedBox( SizedBox(
width: 700, width: 700,
child: MihBusinessCard( child: MihBusinessCard(
businessName: widget.arguments.business!.Name, // businessid: widget.arguments.business!.business_id,
cellNumber: widget.arguments.business!.contact_no, // businessName: widget.arguments.business!.Name,
email: widget.arguments.business!.bus_email, // cellNumber: widget.arguments.business!.contact_no,
gpsLocation: widget.arguments.business!.gps_location, // email: widget.arguments.business!.bus_email,
//To-Do: Add the business Website // gpsLocation: widget.arguments.business!.gps_location,
website: widget.arguments.business!.website, // rating: widget.arguments.business!.rating.isNotEmpty
// ? double.parse(widget.arguments.business!.rating)
// : 0,
// website: widget.arguments.business!.website,
business: widget.arguments.business!,
startUpSearch: null,
width: width,
), ),
), ),
const SizedBox(height: 30.0), const SizedBox(height: 30.0),

View File

@@ -11,9 +11,11 @@ import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_
class MihBusinessDetailsView extends StatefulWidget { class MihBusinessDetailsView extends StatefulWidget {
final Business business; final Business business;
final String? startUpSearch;
const MihBusinessDetailsView({ const MihBusinessDetailsView({
super.key, super.key,
required this.business, required this.business,
required this.startUpSearch,
}); });
@override @override
@@ -162,12 +164,18 @@ class _MihBusinessDetailsViewState extends State<MihBusinessDetailsView> {
SizedBox( SizedBox(
width: 700, width: 700,
child: MihBusinessCard( child: MihBusinessCard(
businessName: widget.business.Name, business: widget.business,
cellNumber: widget.business.contact_no, startUpSearch: widget.startUpSearch,
email: widget.business.bus_email, // businessid: widget.business.business_id,
gpsLocation: widget.business.gps_location, // businessName: widget.business.Name,
//To-Do: Add the business Website // cellNumber: widget.business.contact_no,
website: widget.business.website, // email: widget.business.bus_email,
// gpsLocation: widget.business.gps_location,
// rating: widget.business.rating.isNotEmpty
// ? double.parse(widget.business.rating)
// : 0,
// website: widget.business.website,
width: width,
), ),
), ),
], ],

View File

@@ -0,0 +1,160 @@
import 'package:custom_rating_bar/custom_rating_bar.dart';
import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/main.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_pop_up_messages/mih_loading_circle.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';
class MihBusinessReviews extends StatefulWidget {
final Business business;
const MihBusinessReviews({
super.key,
required this.business,
});
@override
State<MihBusinessReviews> createState() => _MihBusinessReviewsState();
}
class _MihBusinessReviewsState extends State<MihBusinessReviews> {
// late Future<List<BusinessReview>> _reviews;
// @override
// void initState() {
// super.initState();
// _reviews = MihMzansiDirectoryServices().getAllReviewsofBusiness(
// widget.businessId,
// );
// }
void onReviewTap(BusinessReview? businessReview, double width) {
// showDialog(context: context, builder: (context)=> )
showDialog(
context: context,
builder: (context) {
return MihReviewBusinessWindow(
business: widget.business,
businessReview: businessReview,
screenWidth: width,
readOnly: true,
startUpSearch: null,
);
},
);
}
@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return FutureBuilder(
future: MihMzansiDirectoryServices().getAllReviewsofBusiness(
widget.business.business_id,
),
builder: (context, asyncSnapshot) {
if (asyncSnapshot.connectionState == ConnectionState.waiting) {
return const Mihloadingcircle();
} else if (asyncSnapshot.connectionState == ConnectionState.done &&
asyncSnapshot.hasData) {
List<BusinessReview> reviews = asyncSnapshot.data!;
if (reviews.isEmpty) {
return Column(
children: [
const SizedBox(height: 50),
Icon(
Icons.star_rate_rounded,
size: 150,
color:
MzansiInnovationHub.of(context)!.theme.secondaryColor(),
),
Text(
"No reviews yet, be the first the review\n${widget.business.Name}",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
);
} else {
int descriptionDisplayCOunt = 75;
return ListView.separated(
itemCount: reviews.length,
separatorBuilder: (context, index) => Divider(),
itemBuilder: (context, index) {
return ListTile(
onTap: () {
onReviewTap(reviews[index], screenWidth);
},
title: RatingBar.readOnly(
size: 25,
alignment: Alignment.centerLeft,
filledIcon: Icons.star,
emptyIcon: Icons.star_border,
halfFilledIcon: Icons.star_half,
filledColor: const Color(0xffe9e8a1),
// MzansiInnovationHub.of(context)!.theme.primaryColor(),
emptyColor: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
halfFilledColor: const Color(0xffe9e8a1),
// MzansiInnovationHub.of(context)!.theme.primaryColor(),
// filledColor:
// MzansiInnovationHub.of(context)!.theme.secondaryColor(),
// emptyColor:
// MzansiInnovationHub.of(context)!.theme.secondaryColor(),
// halfFilledColor:
// MzansiInnovationHub.of(context)!.theme.secondaryColor(),
isHalfAllowed: true,
initialRating: double.parse(reviews[index].rating_score),
maxRating: 5,
),
subtitle: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
// Text(
// "${reviews[index].reviewer} ",
// style: TextStyle(
// fontSize: 15,
// fontWeight: FontWeight.bold,
// ),
// ),
Text(
reviews[index].rating_title,
softWrap: true,
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 20,
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,
),
),
Text(
"${reviews[index].date_time.split("T")[0]} ",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.normal,
),
),
],
),
);
},
);
}
} else {
return Center(child: Text('Error'));
}
});
}
}

View File

@@ -55,4 +55,66 @@ class MihAlertServices {
}, },
); );
} }
void successAlert(String title, String message, BuildContext context) {
showDialog(
context: context,
builder: (context) {
return MihPackageAlert(
alertIcon: Icon(
Icons.check_circle_outline_rounded,
size: 150,
color: MzansiInnovationHub.of(context)!.theme.successColor(),
),
alertTitle: title,
alertBody: Column(
children: [
Text(
message,
style: TextStyle(
color:
MzansiInnovationHub.of(context)!.theme.secondaryColor(),
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 25),
],
),
alertColour: MzansiInnovationHub.of(context)!.theme.successColor(),
);
},
);
}
void errorAlert(String title, String message, BuildContext context) {
showDialog(
context: context,
builder: (context) {
return MihPackageAlert(
alertIcon: Icon(
Icons.warning_amber_rounded,
size: 150,
color: MzansiInnovationHub.of(context)!.theme.errorColor(),
),
alertTitle: title,
alertBody: Column(
children: [
Text(
message,
style: TextStyle(
color:
MzansiInnovationHub.of(context)!.theme.secondaryColor(),
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 25),
],
),
alertColour: MzansiInnovationHub.of(context)!.theme.errorColor(),
);
},
);
}
} }

View File

@@ -0,0 +1,128 @@
import 'dart:convert';
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;
class MihMzansiDirectoryServices {
final baseAPI = AppEnviroment.baseApiUrl;
Future<BusinessReview?> getUserReviewOfBusiness(
String app_id,
String business_id,
) async {
final response = await http.get(Uri.parse(
"${AppEnviroment.baseApiUrl}/mzansi-directory/business-ratings/user/$app_id/$business_id"));
print(response.statusCode);
if (response.statusCode == 200) {
String body = response.body;
var jsonBody = jsonDecode(body);
BusinessReview? busRev = BusinessReview.fromJson(jsonBody);
return busRev;
} else {
return null;
}
}
Future<List<BusinessReview>> getAllReviewsofBusiness(
String business_id,
) async {
final response = await http.get(Uri.parse(
"${AppEnviroment.baseApiUrl}/mzansi-directory/business-ratings/all/$business_id"));
if (response.statusCode == 200) {
Iterable l = jsonDecode(response.body);
List<BusinessReview> businessReviews = List<BusinessReview>.from(
l.map((model) => BusinessReview.fromJson(model)));
return businessReviews;
} else {
throw Exception('failed to fetch Business Reviews');
}
}
Future<int> addBusinessReview(
String app_id,
String business_id,
String rating_title,
String rating_description,
String rating_score,
String current_rating,
) async {
var response = await http.post(
Uri.parse(
"${AppEnviroment.baseApiUrl}/mzansi-directory/business-rating/insert/"),
headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8"
},
body: jsonEncode(<String, dynamic>{
"app_id": app_id,
"business_id": business_id,
"rating_title": rating_title,
"rating_description": rating_description,
"rating_score": rating_score,
"current_rating": current_rating,
}),
);
if (response.statusCode == 201) {
return response.statusCode;
} else {
return response.statusCode;
}
}
Future<int> deleteBusinessReview(
int idbusiness_ratings,
String business_id,
String rating_score,
String current_rating,
) async {
var response = await http.delete(
Uri.parse(
"${AppEnviroment.baseApiUrl}/mzansi-directory/business-rating/delete/"),
headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8"
},
body: jsonEncode(<String, dynamic>{
"idbusiness_ratings": idbusiness_ratings,
"business_id": business_id,
"rating_score": rating_score,
"current_rating": current_rating,
}),
);
if (response.statusCode == 200) {
return response.statusCode;
} else {
return response.statusCode;
}
}
Future<int> updateBusinessReview(
int idbusiness_ratings,
String business_id,
String rating_title,
String rating_description,
String rating_new_score,
String rating_old_score,
String current_rating,
) async {
var response = await http.put(
Uri.parse(
"${AppEnviroment.baseApiUrl}/mzansi-directory/business-rating/update/"),
headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8"
},
body: jsonEncode(<String, dynamic>{
"idbusiness_ratings": idbusiness_ratings,
"business_id": business_id,
"rating_title": rating_title,
"rating_description": rating_description,
"rating_new_score": rating_new_score,
"rating_old_score": rating_old_score,
"current_rating": current_rating,
}),
);
if (response.statusCode == 200) {
return response.statusCode;
} else {
return response.statusCode;
}
}
}

View File

@@ -265,6 +265,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
custom_rating_bar:
dependency: "direct main"
description:
name: custom_rating_bar
sha256: c2dafa488843b29f50ac2c2905b6a4cefd2cb603de31c6224adb047b6ed8593e
url: "https://pub.dev"
source: hosted
version: "3.0.0"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:

View File

@@ -45,6 +45,7 @@ dependencies:
pwa_install: ^0.0.6 pwa_install: ^0.0.6
google_mobile_ads: ^6.0.0 google_mobile_ads: ^6.0.0
redacted: ^1.0.13 redacted: ^1.0.13
custom_rating_bar: ^3.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

2
backend/.gitignore vendored
View File

@@ -1,3 +1,3 @@
database/__pycache__/ .env
__pycache__/ __pycache__/
temp*.pdf temp*.pdf

View File

@@ -1,19 +1,25 @@
from minio import Minio from minio import Minio
import os
from dotenv import load_dotenv
load_dotenv()
minioAccess = os.getenv("MINIO_ACCESS_KEY")
minioSecret = os.getenv("MINIO_SECRET_KEY")
def minioConnect(env): def minioConnect(env):
if(env == "Dev"): if(env == "Dev"):
return Minio( return Minio(
"minio:9000", "minio:9000",
# "minio.mzansi-innovation-hub.co.za", # "minio.mzansi-innovation-hub.co.za",
access_key="0RcgutfvcDq28lz7", access_key=minioAccess,
secret_key="nEED72ZlKYgDqH9Iy46fVGGT9TfabGWO", secret_key=minioSecret,
secure=False secure=False
) )
else: else:
return Minio( return Minio(
#"minio:9000", #"minio:9000",
"minio.mzansi-innovation-hub.co.za", "minio.mzansi-innovation-hub.co.za",
access_key="0RcgutfvcDq28lz7", access_key=minioAccess,
secret_key="nEED72ZlKYgDqH9Iy46fVGGT9TfabGWO", secret_key=minioSecret,
secure=True secure=True
) )

View File

@@ -1,48 +1,62 @@
import mysql.connector import mysql.connector
import os
from dotenv import load_dotenv
load_dotenv()
dbUser = os.getenv("DB_USER")
dbPass = os.getenv("DB_PASSWD")
def dbPatientManagerConnect(): def dbPatientManagerConnect():
return mysql.connector.connect( return mysql.connector.connect(
host="mysqldb", host="mysqldb",
user="root", user=dbUser,
passwd="C@rtoon1995", passwd=dbPass,
database="patient_manager" database="patient_manager"
) )
def dbAppDataConnect(): def dbAppDataConnect():
return mysql.connector.connect( return mysql.connector.connect(
host="mysqldb", host="mysqldb",
user="root", user=dbUser,
passwd="C@rtoon1995", passwd=dbPass,
database="app_data" database="app_data"
) )
def dbDataAccessConnect(): def dbDataAccessConnect():
return mysql.connector.connect( return mysql.connector.connect(
host="mysqldb", host="mysqldb",
user="root", user=dbUser,
passwd="C@rtoon1995", passwd=dbPass,
database="data_access" database="data_access"
) )
def dbMzansiWalletConnect(): def dbMzansiWalletConnect():
return mysql.connector.connect( return mysql.connector.connect(
host="mysqldb", host="mysqldb",
user="root", user=dbUser,
passwd="C@rtoon1995", passwd=dbPass,
database="mzansi_wallet" database="mzansi_wallet"
) )
def dbMzansiDirectoryConnect():
return mysql.connector.connect(
host="mysqldb",
user=dbUser,
passwd=dbPass,
database="mzansi_directory"
)
def dbMzansiCalendarConnect(): def dbMzansiCalendarConnect():
return mysql.connector.connect( return mysql.connector.connect(
host="mysqldb", host="mysqldb",
user="root", user=dbUser,
passwd="C@rtoon1995", passwd=dbPass,
database="mzansi_calendar" database="mzansi_calendar"
) )
def dbAllConnect(): def dbAllConnect():
return mysql.connector.connect( return mysql.connector.connect(
host="mysqldb", host="mysqldb",
user="root", user=dbUser,
passwd="C@rtoon1995", passwd=dbPass,
) )

View File

@@ -17,6 +17,7 @@ import routers.business as business
import routers.access_request as access_request import routers.access_request as access_request
import routers.patient_access as patient_access import routers.patient_access as patient_access
import routers.mzansi_wallet as mzansi_wallet import routers.mzansi_wallet as mzansi_wallet
import routers.mzansi_directory as mzansi_directory
import routers.icd10_codes as icd10_codes import routers.icd10_codes as icd10_codes
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware import Middleware from fastapi.middleware import Middleware
@@ -92,6 +93,7 @@ app.include_router(business_user.router)
app.include_router(business.router) app.include_router(business.router)
app.include_router(notifications.router) app.include_router(notifications.router)
app.include_router(mzansi_wallet.router) app.include_router(mzansi_wallet.router)
app.include_router(mzansi_directory.router)
app.include_router(icd10_codes.router) app.include_router(icd10_codes.router)
app.include_router(appointments.router) app.include_router(appointments.router)

View File

@@ -6,5 +6,6 @@ reportlab
requests requests
watchfiles watchfiles
python-multipart python-multipart
python-dotenv
xlrd xlrd
supertokens-python==0.29.2 supertokens-python==0.29.2

View File

@@ -72,9 +72,10 @@ async def read_all_businesses(search: str, session: SessionContainer = Depends(v
query += "practice_no, vat_no, " query += "practice_no, vat_no, "
query += "website, rating, mission_vision " query += "website, rating, mission_vision "
query += "FROM business " query += "FROM business "
query += "WHERE LOWER(business.Name) LIKE %s OR LOWER(business.type) LIKE %s" 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 search_term = f"%{search.lower()}%" # Add wildcards and lowercase
cursor.execute(query, (search_term, search_term)) cursor.execute(query, (search_term, search_term, search_term, search_term))
items = [ items = [
{ {
"business_id": item[0], "business_id": item[0],

View File

@@ -0,0 +1,236 @@
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from datetime import datetime
import database
from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.session import SessionContainer
from fastapi import Depends
router = APIRouter()
# class BusinessRatingUserGet(BaseModel):
# app_id: str
# business_id: str
class BusinessRatingInsertRequest(BaseModel):
app_id: str
business_id: str
rating_title: str
rating_description: str
rating_score: str
current_rating: str
class BusinessRatingDeleteRequest(BaseModel):
idbusiness_ratings: int
business_id: str
rating_score: str
current_rating: str
class BusinessRatingUpdateRequest(BaseModel):
idbusiness_ratings: int
business_id: str
rating_title: str
rating_description: str
rating_new_score: str
rating_old_score: str
current_rating: str
@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())
db = database.dbConnection.dbAllConnect()
cursor = db.cursor()
query = ""
query += "SELECT business_ratings.idbusiness_ratings, business_ratings.app_id, business_ratings.business_id, "
query += "business_ratings.rating_title, business_ratings.rating_description, business_ratings.rating_score, "
query += "business_ratings.date_time, users.username as 'reviewer' "
query += "FROM mzansi_directory.business_ratings "
query += "inner join app_data.users "
query += "on business_ratings.app_id = users.app_id "
query += "where business_ratings.business_id = %s and business_ratings.app_id = %s;"
cursor.execute(query, (business_id,
app_id,))
item = cursor.fetchone() # Get only one row
cursor.close()
db.close()
if item:
# Return a single dictionary
return {
"idbusiness_ratings": item[0],
"app_id": item[1],
"business_id": item[2],
"rating_title": item[3],
"rating_description": item[4],
"rating_score": item[5],
"date_time": item[6],
"reviewer": item[7],
}
else:
# Return an empty response or a specific message
return None
# items = [
# {
# "idbusiness_ratings": item[0],
# "app_id": item[1],
# "business_id": item[2],
# "rating_title": item[3],
# "rating_description": item[4],
# "rating_score": item[5],
# "date_time": item[6],
# "reviewer": item[7],
# }
# for item in cursor.fetchall()
# ]
# cursor.close()
# db.close()
# return items[0]
@router.get("/mzansi-directory/business-ratings/all/{business_id}", tags=["Mzansi Directory"])
async def read_all_ratings_by_business_id(business_id: str, session: SessionContainer = Depends(verify_session())): # , session: SessionContainer = Depends(verify_session())
db = database.dbConnection.dbAllConnect()
cursor = db.cursor()
query = ""
query += "SELECT business_ratings.idbusiness_ratings, business_ratings.app_id, business_ratings.business_id, "
query += "business_ratings.rating_title, business_ratings.rating_description, business_ratings.rating_score, "
query += "business_ratings.date_time, users.username as 'reviewer' "
query += "FROM mzansi_directory.business_ratings "
query += "inner join app_data.users "
query += "on business_ratings.app_id = users.app_id "
query += "where business_ratings.business_id = %s "
query += "order by business_ratings.date_time desc;"
cursor.execute(query, (business_id,))
items = [
{
"idbusiness_ratings": item[0],
"app_id": item[1],
"business_id": item[2],
"rating_title": item[3],
"rating_description": item[4],
"rating_score": item[5],
"date_time": item[6],
"reviewer": item[7],
}
for item in cursor.fetchall()
]
cursor.close()
db.close()
return items
@router.post("/mzansi-directory/business-rating/insert/", tags=["Mzansi Directory"], status_code=201)
async def insert_loyalty_card(itemRequest : BusinessRatingInsertRequest): #, session: SessionContainer = Depends(verify_session())
db = database.dbConnection.dbAllConnect()
nowDateTime = datetime.now()
formatedDateTime = nowDateTime.strftime("%Y-%m-%d %H:%M:%S")
cursor = db.cursor()
try:
# Get No Of reviews for business
businessReviewCountQuery = "select count(*) from mzansi_directory.business_ratings where business_ratings.business_id = %s"
countData = (itemRequest.business_id,)
cursor.execute(businessReviewCountQuery, countData)
countResult = cursor.fetchone()
row_count = countResult[0] if countResult else 0
print(f"Number of rows in business_ratings: {row_count}")
# add business rating
addQuery = "insert into mzansi_directory.business_ratings "
addQuery += "(business_ratings.app_id, business_ratings.business_id, business_ratings.rating_title, business_ratings.rating_description, business_ratings.rating_score, business_ratings.date_time) "
addQuery += "values (%s, %s, %s, %s, %s, %s)"
addQueryData = (itemRequest.app_id,
itemRequest.business_id,
itemRequest.rating_title,
itemRequest.rating_description,
itemRequest.rating_score,
formatedDateTime,
)
cursor.execute(addQuery, addQueryData)
# Calc New Rating and update business rating
newRating = ((float(itemRequest.current_rating) * row_count) + float(itemRequest.rating_score)) / (row_count + 1)
print(f"New Rating: {newRating}")
updateBusinessQuery = "update app_data.business "
updateBusinessQuery += "set rating = %s "
updateBusinessQuery += "where business_id = %s"
updateBusinessData = (newRating, itemRequest.business_id)
cursor.execute(updateBusinessQuery, updateBusinessData)
db.commit()
except Exception as error:
print(error)
raise HTTPException(status_code=404, detail="Failed to Create Record")
# return {"message": error}
cursor.close()
db.close()
return {"message": "Successfully Created Record"}
@router.delete("/mzansi-directory/business-rating/delete/", tags=["Mzansi Directory"])
async def Delete_loyalty_card(itemRequest : BusinessRatingDeleteRequest, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session())
db = database.dbConnection.dbAllConnect()
cursor = db.cursor()
try:
# Get No Of reviews for business
businessReviewCountQuery = "select count(*) from mzansi_directory.business_ratings where business_ratings.business_id = %s"
countData = (itemRequest.business_id,)
cursor.execute(businessReviewCountQuery, countData)
countResult = cursor.fetchone()
row_count = countResult[0] if countResult else 0
print(f"Number of rows in business_ratings: {row_count}")
# Delete business rating
query = "delete from mzansi_directory.business_ratings "
query += "where business_ratings.idbusiness_ratings=%s"
cursor.execute(query, (str(itemRequest.idbusiness_ratings),))
# Calc New Rating and update business rating
if(row_count <= 1):
newRating = 0.0
else:
newRating = ((float(itemRequest.current_rating) * row_count) - float(itemRequest.rating_score)) / (row_count - 1)
print(f"New Rating: {newRating}")
updateBusinessQuery = "update app_data.business "
updateBusinessQuery += "set rating = %s "
updateBusinessQuery += "where business_id = %s"
updateBusinessData = (newRating, itemRequest.business_id)
cursor.execute(updateBusinessQuery, updateBusinessData)
db.commit()
except Exception as error:
print(error)
raise HTTPException(status_code=404, detail="Failed to Delete Record")
cursor.close()
db.close()
return {"message": "Successfully deleted Record"}
@router.put("/mzansi-directory/business-rating/update/", tags=["Mzansi Directory"])
async def UpdatePatient(itemRequest : BusinessRatingUpdateRequest, session: SessionContainer = Depends(verify_session())):
db = database.dbConnection.dbMzansiDirectoryConnect()
cursor = db.cursor()
nowDateTime = datetime.now()
formatedDateTime = nowDateTime.strftime("%Y-%m-%d %H:%M:%S")
try:
# Get No Of reviews for business
businessReviewCountQuery = "select count(*) from mzansi_directory.business_ratings where business_ratings.business_id = %s"
countData = (itemRequest.business_id,)
cursor.execute(businessReviewCountQuery, countData)
countResult = cursor.fetchone()
row_count = countResult[0] if countResult else 0
print(f"Number of rows in business_ratings: {row_count}")
# Update business rating
query = "update business_ratings "
query += "set rating_title=%s, rating_description=%s, rating_score=%s, date_time=%s "
query += "where idbusiness_ratings=%s"
notetData = (itemRequest.rating_title,
itemRequest.rating_description,
itemRequest.rating_new_score,
formatedDateTime,
itemRequest.idbusiness_ratings,
)
cursor.execute(query, notetData)
# Calc New Rating and update business rating
# add new rating and old rating params
newRating = ((float(itemRequest.current_rating) * row_count) - float(itemRequest.rating_old_score) + float(itemRequest.rating_new_score)) / (row_count)
print(f"New Rating: {newRating}")
updateBusinessQuery = "update app_data.business "
updateBusinessQuery += "set rating = %s "
updateBusinessQuery += "where business_id = %s"
updateBusinessData = (newRating, itemRequest.business_id)
cursor.execute(updateBusinessQuery, updateBusinessData)
db.commit()
except Exception as error:
raise HTTPException(status_code=404, detail="Failed to Update Record")
cursor.close()
db.close()
return {"message": "Successfully Updated Record"}

View File

@@ -74,10 +74,12 @@ async def read_all_users(search: str, session: SessionContainer = Depends(verify
cursor = db.cursor() cursor = db.cursor()
query = "" query = ""
query += "SELECT * FROM users " query += "SELECT * FROM users "
query += "WHERE (LOWER(email) LIKE %s OR LOWER(username) LIKE %s) " query += "WHERE (LOWER(email) LIKE %s OR LOWER(username) LIKE %s "
query += "OR LOWER(fname) LIKE %s OR LOWER(lname) LIKE %s "
query += "OR LOWER(purpose) LIKE %s) "
query += "AND username != ''" query += "AND username != ''"
search_term = f"%{search.lower()}%" # Add wildcards and lowercase search_term = f"%{search.lower()}%" # Add wildcards and lowercase
cursor.execute(query, (search_term, search_term)) cursor.execute(query, (search_term, search_term,search_term, search_term, search_term))
items = [ items = [
{ {
"idUser": item[0], "idUser": item[0],