From a841f9bbd356bbc6b8b476ffafb2b26f5825e492 Mon Sep 17 00:00:00 2001 From: yaso Date: Wed, 12 Mar 2025 13:47:07 +0200 Subject: [PATCH 1/4] Mih App alignment --- .../mzansi_business_profile.dart | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Frontend/lib/mih_packages/mzansi_profile/business_profile/mzansi_business_profile.dart diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/mzansi_business_profile.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/mzansi_business_profile.dart new file mode 100644 index 00000000..e4b75b85 --- /dev/null +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/mzansi_business_profile.dart @@ -0,0 +1,79 @@ +import 'package:Mzansi_Innovation_Hub/mih_components/mih_package/mih_app.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_package/mih_app_action.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_package/mih_app_tools.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/arguments.dart'; +import 'package:Mzansi_Innovation_Hub/mih_packages/mzansi_profile/business_profile/app_tools/mih_business_profile.dart'; +import 'package:Mzansi_Innovation_Hub/mih_packages/mzansi_profile/business_profile/app_tools/mih_business_user_search.dart'; +import 'package:Mzansi_Innovation_Hub/mih_packages/mzansi_profile/business_profile/app_tools/mih_my_business_team.dart'; +import 'package:flutter/material.dart'; + +class MzansiBusinessProfile extends StatefulWidget { + final BusinessArguments arguments; + const MzansiBusinessProfile({ + super.key, + required this.arguments, + }); + + @override + State createState() => _MzansiBusinessProfileState(); +} + +class _MzansiBusinessProfileState extends State { + int _selcetedIndex = 0; + @override + Widget build(BuildContext context) { + return MihApp( + appActionButton: getAction(), + appTools: getTools(), + appBody: getToolBody(), + selectedbodyIndex: _selcetedIndex, + onIndexChange: (newValue) { + setState(() { + _selcetedIndex = newValue; + }); + }, + ); + } + + MihAppAction getAction() { + return MihAppAction( + icon: const Icon(Icons.arrow_back), + iconSize: 35, + onTap: () { + Navigator.of(context).pop(); + }, + ); + } + + MihAppTools getTools() { + Map temp = {}; + temp[const Icon(Icons.business)] = () { + setState(() { + _selcetedIndex = 0; + }); + }; + temp[const Icon(Icons.people_outline)] = () { + setState(() { + _selcetedIndex = 1; + }); + }; + temp[const Icon(Icons.add)] = () { + setState(() { + _selcetedIndex = 2; + }); + }; + return MihAppTools( + tools: temp, + selcetedIndex: _selcetedIndex, + ); + } + + List getToolBody() { + List toolBodies = [ + MihBusinessProfile(arguments: widget.arguments), + MihMyBusinessTeam(arguments: widget.arguments), + MihBusinessUserSearch(arguments: widget.arguments), + ]; + return toolBodies; + } +} From 4dcd31cdbb7e007be0795142dab848d0aa3a93ba Mon Sep 17 00:00:00 2001 From: yaso Date: Wed, 12 Mar 2025 13:47:38 +0200 Subject: [PATCH 2/4] recreate business prof tools --- .../app_tools/mih_business_profile.dart | 573 ++++++++++++++++++ .../app_tools/mih_business_user_search.dart | 161 +++++ .../app_tools/mih_my_business_team.dart | 124 ++++ 3 files changed, 858 insertions(+) create mode 100644 Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_business_profile.dart create mode 100644 Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_business_user_search.dart create mode 100644 Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_my_business_team.dart diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_business_profile.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_business_profile.dart new file mode 100644 index 00000000..eda76b95 --- /dev/null +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_business_profile.dart @@ -0,0 +1,573 @@ +import 'dart:convert'; + +import 'package:Mzansi_Innovation_Hub/main.dart'; +import 'package:Mzansi_Innovation_Hub/mih_apis/mih_location_api.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_button.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_dropdown_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_file_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_text_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_package/mih-app_tool_body.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_error_message.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_success_message.dart'; +import 'package:Mzansi_Innovation_Hub/mih_env/env.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/arguments.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:supertokens_flutter/http.dart' as http; +import 'package:http/http.dart' as http2; +import 'package:supertokens_flutter/supertokens.dart'; + +class MihBusinessProfile extends StatefulWidget { + final BusinessArguments arguments; + const MihBusinessProfile({ + super.key, + required this.arguments, + }); + + @override + State createState() => _MihBusinessProfileState(); +} + +class _MihBusinessProfileState extends State { + final FocusNode _focusNode = FocusNode(); + final baseAPI = AppEnviroment.baseApiUrl; + + final nameController = TextEditingController(); + final typeController = TextEditingController(); + final regController = TextEditingController(); + final logonameController = TextEditingController(); + final fnameController = TextEditingController(); + final lnameController = TextEditingController(); + final titleController = TextEditingController(); + final signtureController = TextEditingController(); + final accessController = TextEditingController(); + final contactController = TextEditingController(); + final emailController = TextEditingController(); + final locationController = TextEditingController(); + final practiceNoController = TextEditingController(); + final vatNoController = TextEditingController(); + + late PlatformFile? selectedLogo = null; + late PlatformFile? selectedSignature = null; + + final ValueNotifier busType = ValueNotifier(""); + + late String business_id; + late String oldLogoPath; + late String oldSigPath; + + Future updateBusinessProfileAPICall(String business_id) async { + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + var response = await http.put( + Uri.parse("$baseAPI/business/update/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({ + "business_id": business_id, + "Name": nameController.text, + "type": typeController.text, + "registration_no": regController.text, + "logo_name": logonameController.text, + "logo_path": + "${widget.arguments.signedInUser.app_id}/business_files/${logonameController.text}", + "contact_no": contactController.text, + "bus_email": emailController.text, + "gps_location": locationController.text, + "practice_no": practiceNoController.text, + "vat_no": vatNoController.text, + }), + ); + if (response.statusCode == 200) { + //var businessResponse = jsonDecode(response.body); + //print(selectedLogo != null); + if (selectedLogo != null) { + uploadSelectedFile(selectedLogo, logonameController); + deleteFileApiCall(oldLogoPath); + } + updateBusinessUserAPICall(business_id); + } else { + internetConnectionPopUp(); + } + } + + Future updateBusinessUserAPICall(String business_id) async { + var response = await http.put( + Uri.parse("$baseAPI/business-user/update/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({ + "business_id": business_id, + "app_id": widget.arguments.signedInUser.app_id, + "signature": signtureController.text, + "sig_path": + "${widget.arguments.signedInUser.app_id}/business_files/${signtureController.text}", + "title": titleController.text, + "access": accessController.text, + }), + ); + if (response.statusCode == 200) { + if (selectedSignature != null) { + uploadSelectedFile(selectedSignature, signtureController); + deleteFileApiCall(oldSigPath); + } + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pushNamed( + '/business-profile/manage', + arguments: BusinessArguments( + widget.arguments.signedInUser, + widget.arguments.businessUser, + widget.arguments.business, + ), + ); + String message = + "Your business profile is now live! You can now start connecting with customers and growing your business."; + successPopUp(message); + } else { + internetConnectionPopUp(); + } + } + + Future uploadSelectedFile( + PlatformFile? file, TextEditingController controller) async { + //to-do delete file when changed + var token = await SuperTokens.getAccessToken(); + //print(t); + //print("here1"); + var request = http2.MultipartRequest( + 'POST', Uri.parse("${AppEnviroment.baseApiUrl}/minio/upload/file/")); + request.headers['accept'] = 'application/json'; + request.headers['Authorization'] = 'Bearer $token'; + request.headers['Content-Type'] = 'multipart/form-data'; + request.fields['app_id'] = widget.arguments.signedInUser.app_id; + request.fields['folder'] = "business_files"; + request.files.add(await http2.MultipartFile.fromBytes('file', file!.bytes!, + filename: file.name.replaceAll(RegExp(r' '), '-'))); + var response1 = await request.send(); + if (response1.statusCode == 200) { + } else { + internetConnectionPopUp(); + } + } + + Future deleteFileApiCall(String filePath) async { + // delete file from minio + var response = await http.delete( + Uri.parse("$baseAPI/minio/delete/file/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({"file_path": filePath}), + ); + //print("Here4"); + //print(response.statusCode); + if (response.statusCode == 200) { + //SQL delete + } else { + internetConnectionPopUp(); + } + } + + bool isFullAccess() { + if (widget.arguments.businessUser!.access == "Partial") { + return false; + } else { + return true; + } + } + + bool validEmail() { + String text = emailController.text; + var regex = RegExp(r'^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$'); + return regex.hasMatch(text); + } + + bool isFieldsFilled() { + if (nameController.text.isEmpty || + typeController.text.isEmpty || + regController.text.isEmpty || + logonameController.text.isEmpty || + fnameController.text.isEmpty || + lnameController.text.isEmpty || + titleController.text.isEmpty || + signtureController.text.isEmpty || + accessController.text.isEmpty || + contactController.text.isEmpty || + emailController.text.isEmpty) { + return false; + } else { + return true; + } + } + + void typeSelected() { + if (typeController.text.isNotEmpty) { + busType.value = typeController.text; + } else { + busType.value = ""; + } + } + + void submitForm(String business_id) { + if (!validEmail()) { + emailError(); + } else if (isFieldsFilled()) { + updateBusinessProfileAPICall(business_id); + } else { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Input Error"); + }, + ); + } + } + + void successPopUp(String message) { + showDialog( + context: context, + builder: (context) { + return MIHSuccessMessage( + successType: "Success", + successMessage: message, + ); + }, + ); + } + + void emailError() { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Invalid Email"); + }, + ); + } + + void internetConnectionPopUp() { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Internet Connection"); + }, + ); + } + + @override + void dispose() { + nameController.dispose(); + typeController.dispose(); + regController.dispose(); + logonameController.dispose(); + fnameController.dispose(); + lnameController.dispose(); + titleController.dispose(); + signtureController.dispose(); + accessController.dispose(); + contactController.dispose(); + emailController.dispose(); + locationController.dispose(); + practiceNoController.dispose(); + vatNoController.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + void initState() { + typeController.addListener(typeSelected); + setState(() { + titleController.text = widget.arguments.businessUser!.title; + fnameController.text = widget.arguments.signedInUser.fname; + lnameController.text = widget.arguments.signedInUser.lname; + signtureController.text = widget.arguments.businessUser!.signature; + titleController.text = widget.arguments.businessUser!.title; + accessController.text = widget.arguments.businessUser!.access; + oldSigPath = widget.arguments.businessUser!.sig_path; + business_id = widget.arguments.business!.business_id; + regController.text = widget.arguments.business!.registration_no; + nameController.text = widget.arguments.business!.Name; + typeController.text = widget.arguments.business!.type; + logonameController.text = widget.arguments.business!.logo_name; + oldLogoPath = widget.arguments.business!.logo_path; + contactController.text = widget.arguments.business!.contact_no; + emailController.text = widget.arguments.business!.bus_email; + locationController.text = widget.arguments.business!.gps_location; + practiceNoController.text = widget.arguments.business!.practice_no; + vatNoController.text = widget.arguments.business!.vat_no; + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return MihAppToolBody( + borderOn: true, + bodyItem: getBody(), + ); + } + + Widget getBody() { + return ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: SingleChildScrollView( + child: SafeArea( + child: KeyboardListener( + focusNode: _focusNode, + autofocus: true, + onKeyEvent: (event) async { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.enter) { + //print(business_id); + submitForm(business_id); + } + }, + child: Column( + children: [ + Visibility( + visible: isFullAccess(), + child: Column( + children: [ + const Text( + "Business Profile", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 25, + ), + ), + Divider( + color: MzanziInnovationHub.of(context) + ?.theme + .secondaryColor(), + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: regController, + hintText: "Registration No.", + editable: true, + required: true, + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: nameController, + hintText: "Business Name", + editable: true, + required: true, + ), + const SizedBox(height: 10.0), + MIHDropdownField( + controller: typeController, + hintText: "Business Type", + dropdownOptions: const ["Doctors Office", "Other"], + required: true, + editable: true, + enableSearch: false, + ), + const SizedBox(height: 10.0), + ValueListenableBuilder( + valueListenable: busType, + builder: (BuildContext context, String value, + Widget? child) { + return Visibility( + visible: value == "Doctors Office", + child: MIHTextField( + controller: practiceNoController, + hintText: "Practice Number", + editable: true, + required: true, + ), + ); + }, + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: vatNoController, + hintText: "VAT Number", + editable: true, + required: true, + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: contactController, + hintText: "Contact Number", + editable: true, + required: true, + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: emailController, + hintText: "Email", + editable: true, + required: true, + ), + const SizedBox(height: 10.0), + MIHFileField( + controller: logonameController, + hintText: "Logo", + editable: false, + required: true, + onPressed: () async { + FilePickerResult? result = + await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['jpg', 'png', 'pdf'], + ); + if (result == null) return; + final selectedFile = result.files.first; + setState(() { + selectedLogo = selectedFile; + }); + setState(() { + logonameController.text = selectedFile.name; + }); + }, + ), + const SizedBox(height: 10.0), + Row( + children: [ + Flexible( + child: MIHTextField( + controller: locationController, + hintText: "Location", + editable: false, + required: false, + ), + ), + const SizedBox(width: 10.0), + SizedBox( + width: 80.0, + height: 50.0, + child: MIHButton( + buttonText: "Set", + buttonColor: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + textColor: MzanziInnovationHub.of(context)! + .theme + .primaryColor(), + onTap: () { + MIHLocationAPI() + .getGPSPosition(context) + .then((position) { + if (position != null) { + setState(() { + locationController.text = + "${position.latitude}, ${position.longitude}"; + }); + } + }); + }, + ), + ), + ], + ), + const SizedBox(height: 15.0), + ], + ), + ), + Column( + children: [ + //const SizedBox(height: 15.0), + const Text( + "My Business User", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 25, + ), + ), + Divider( + color: MzanziInnovationHub.of(context) + ?.theme + .secondaryColor(), + ), + const SizedBox(height: 10.0), + MIHDropdownField( + controller: titleController, + hintText: "Title", + dropdownOptions: const ["Doctor", "Assistant"], + required: true, + editable: true, + enableSearch: false, + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: fnameController, + hintText: "Name", + editable: false, + required: true, + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: lnameController, + hintText: "Surname", + editable: false, + required: true, + ), + const SizedBox(height: 10.0), + MIHFileField( + controller: signtureController, + hintText: "Signature", + editable: false, + required: true, + onPressed: () async { + FilePickerResult? result = + await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['jpg', 'png', 'pdf'], + ); + if (result == null) return; + final selectedFile = result.files.first; + setState(() { + selectedSignature = selectedFile; + }); + setState(() { + signtureController.text = selectedFile.name; + }); + }, + ), + const SizedBox(height: 15.0), + MIHDropdownField( + controller: accessController, + hintText: "Access", + dropdownOptions: const ["Full", "Partial"], + required: true, + editable: false, + enableSearch: false, + ), + const SizedBox(height: 30.0), + SizedBox( + width: 500.0, + height: 50.0, + child: MIHButton( + buttonText: "Update", + buttonColor: MzanziInnovationHub.of(context)! + .theme + .secondaryColor(), + textColor: MzanziInnovationHub.of(context)! + .theme + .primaryColor(), + onTap: () { + //print(business_id); + submitForm(business_id); + }, + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_business_user_search.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_business_user_search.dart new file mode 100644 index 00000000..f99285f2 --- /dev/null +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_business_user_search.dart @@ -0,0 +1,161 @@ +import 'dart:convert'; + +import 'package:Mzansi_Innovation_Hub/main.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_search_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_package/mih-app_tool_body.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_error_message.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; +import 'package:Mzansi_Innovation_Hub/mih_env/env.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/app_user.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/arguments.dart'; +import 'package:Mzansi_Innovation_Hub/mih_packages/mzansi_profile/business_profile/builders/build_user_list.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:supertokens_flutter/http.dart' as http; + +class MihBusinessUserSearch extends StatefulWidget { + final BusinessArguments arguments; + const MihBusinessUserSearch({ + super.key, + required this.arguments, + }); + + @override + State createState() => _MihBusinessUserSearchState(); +} + +class _MihBusinessUserSearchState extends State { + final FocusNode _focusNode = FocusNode(); + final TextEditingController searchController = TextEditingController(); + late Future> userSearchResults; + + String userSearch = ""; + String errorCode = ""; + String errorBody = ""; + + Future> fetchUsers(String search) async { + final response = await http + .get(Uri.parse("${AppEnviroment.baseApiUrl}/users/search/$search")); + errorCode = response.statusCode.toString(); + errorBody = response.body; + if (response.statusCode == 200) { + Iterable l = jsonDecode(response.body); + List users = + List.from(l.map((model) => AppUser.fromJson(model))); + return users; + } else { + throw Exception('failed to load patients'); + } + } + + void submitUserForm() { + if (searchController.text != "") { + setState(() { + userSearch = searchController.text; + userSearchResults = fetchUsers(userSearch); + }); + } else { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Input Error"); + }, + ); + } + } + + Widget displayUserList(List userList) { + if (userList.isNotEmpty) { + return BuildUserList( + users: userList, + arguments: widget.arguments, + ); + } + return Center( + child: Text( + "Enter Username or Email to search", + style: TextStyle( + fontSize: 25, + color: MzanziInnovationHub.of(context)!.theme.messageTextColor()), + textAlign: TextAlign.center, + ), + ); + } + + @override + void initState() { + super.initState(); + userSearchResults = fetchUsers("abc"); + } + + @override + Widget build(BuildContext context) { + return MihAppToolBody( + borderOn: true, + bodyItem: getBody(), + ); + } + + Widget getBody() { + return KeyboardListener( + focusNode: _focusNode, + autofocus: true, + onKeyEvent: (event) async { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.enter) { + submitUserForm(); + } + }, + child: Column(mainAxisSize: MainAxisSize.max, children: [ + const Text( + "User Search", + style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold), + ), + //spacer + Divider(color: MzanziInnovationHub.of(context)!.theme.secondaryColor()), + const SizedBox(height: 10), + MIHSearchField( + controller: searchController, + hintText: "Username or Email Search", + required: true, + editable: true, + onTap: () { + submitUserForm(); + }, + ), + //spacer + const SizedBox(height: 10), + FutureBuilder( + future: userSearchResults, + builder: (context, snapshot) { + //print("patient Liust ${snapshot.data}"); + if (snapshot.connectionState == ConnectionState.waiting) { + return const Mihloadingcircle(); + } else if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + List patientsList; + if (userSearch == "") { + patientsList = []; + } else { + patientsList = snapshot.data!; + //print(patientsList); + } + return displayUserList(patientsList); + } else { + return Center( + child: Text( + "$errorCode: Error pulling Patients Data\n/patients/search/$userSearch\n$errorBody", + style: TextStyle( + fontSize: 25, + color: + MzanziInnovationHub.of(context)!.theme.errorColor()), + textAlign: TextAlign.center, + ), + ); + } + }, + ), + ]), + ); + } +} diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_my_business_team.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_my_business_team.dart new file mode 100644 index 00000000..11b86bbf --- /dev/null +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/app_tools/mih_my_business_team.dart @@ -0,0 +1,124 @@ +import 'dart:convert'; + +import 'package:Mzansi_Innovation_Hub/main.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_package/mih-app_tool_body.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; +import 'package:Mzansi_Innovation_Hub/mih_env/env.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/arguments.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/business_employee.dart'; +import 'package:Mzansi_Innovation_Hub/mih_packages/mzansi_profile/business_profile/builders/build_employee_list.dart'; +import 'package:flutter/material.dart'; +import 'package:supertokens_flutter/http.dart' as http; + +class MihMyBusinessTeam extends StatefulWidget { + final BusinessArguments arguments; + const MihMyBusinessTeam({ + super.key, + required this.arguments, + }); + + @override + State createState() => _MihMyBusinessTeamState(); +} + +class _MihMyBusinessTeamState extends State { + late Future> employeeList; + + String errorCode = ""; + String errorBody = ""; + + Future> fetchEmployees() async { + //print("Patien manager page: $endpoint"); + final response = await http.get(Uri.parse( + "${AppEnviroment.baseApiUrl}/business-user/employees/${widget.arguments.businessUser!.business_id}")); + errorCode = response.statusCode.toString(); + errorBody = response.body; + if (response.statusCode == 200) { + //print("Here1"); + Iterable l = jsonDecode(response.body); + //print("Here2"); + List patientQueue = List.from( + l.map((model) => BusinessEmployee.fromJson(model))); + //print("Here3"); + //print(patientQueue); + return patientQueue; + } else { + throw Exception('failed to load employees'); + } + } + + Widget displayEmployeeList(List employeeList) { + if (employeeList.isNotEmpty) { + return BuildEmployeeList( + employees: employeeList, + arguments: widget.arguments, + ); + } + return Center( + child: Text( + "", + style: TextStyle( + fontSize: 25, + color: MzanziInnovationHub.of(context)!.theme.messageTextColor()), + textAlign: TextAlign.center, + ), + ); + } + + @override + void initState() { + super.initState(); + employeeList = fetchEmployees(); + } + + @override + Widget build(BuildContext context) { + return MihAppToolBody( + borderOn: true, + bodyItem: getBody(), + ); + } + + Widget getBody() { + return Column(mainAxisSize: MainAxisSize.max, children: [ + const Text( + "Business Team", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 25, + ), + ), + Divider(color: MzanziInnovationHub.of(context)!.theme.secondaryColor()), + const SizedBox(height: 10), + FutureBuilder( + future: employeeList, + builder: (context, snapshot) { + //print("patient Queue List ${snapshot.hasData}"); + if (snapshot.connectionState == ConnectionState.waiting) { + return const Mihloadingcircle(); + } else if (snapshot.connectionState == ConnectionState.done) { + //List employeeListResults; + // if (searchString == "") { + // patientQueueList = []; + // } else { + + // print(patientQueueList); + // } + + return displayEmployeeList(snapshot.requireData); + } else { + return Center( + child: Text( + "$errorCode: Error pulling Patients Data\n${AppEnviroment.baseApiUrl}/business-user/users/${widget.arguments.businessUser!.business_id}\n$errorBody", + style: TextStyle( + fontSize: 25, + color: MzanziInnovationHub.of(context)!.theme.errorColor()), + textAlign: TextAlign.center, + ), + ); + } + }, + ), + ]); + } +} From 97c6d2f53d471b71aa7e2b09e1d9c81d66d65ef1 Mon Sep 17 00:00:00 2001 From: yaso Date: Wed, 12 Mar 2025 13:47:50 +0200 Subject: [PATCH 3/4] recreate builders --- .../builders/build_employee_list.dart | 285 ++++++++++++++++++ .../builders/build_user_list.dart | 237 +++++++++++++++ 2 files changed, 522 insertions(+) create mode 100644 Frontend/lib/mih_packages/mzansi_profile/business_profile/builders/build_employee_list.dart create mode 100644 Frontend/lib/mih_packages/mzansi_profile/business_profile/builders/build_user_list.dart diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/builders/build_employee_list.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/builders/build_employee_list.dart new file mode 100644 index 00000000..96306024 --- /dev/null +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/builders/build_employee_list.dart @@ -0,0 +1,285 @@ +import 'dart:convert'; + +import 'package:Mzansi_Innovation_Hub/main.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_button.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_dropdown_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_text_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_layout/mih_window.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_delete_message.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_error_message.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_success_message.dart'; +import 'package:Mzansi_Innovation_Hub/mih_env/env.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/arguments.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/business_employee.dart'; +import 'package:flutter/material.dart'; +import 'package:supertokens_flutter/http.dart' as http; + +class BuildEmployeeList extends StatefulWidget { + final List employees; + final BusinessArguments arguments; + + const BuildEmployeeList({ + super.key, + required this.employees, + required this.arguments, + }); + + @override + State createState() => _BuildEmployeeListState(); +} + +class _BuildEmployeeListState extends State { + TextEditingController accessController = TextEditingController(); + TextEditingController typeController = TextEditingController(); + TextEditingController fnameController = TextEditingController(); + TextEditingController lnameController = TextEditingController(); + + final baseAPI = AppEnviroment.baseApiUrl; + + Future updateEmployeeAPICall(int index) async { + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + + var response = await http.put( + Uri.parse("$baseAPI/business-user/employees/update/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({ + "business_id": widget.employees[index].business_id, + "app_id": widget.employees[index].app_id, + "title": typeController.text, + "access": accessController.text, + }), + ); + if (response.statusCode == 200) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + //setState(() {}); + Navigator.of(context).pushNamed( + '/business-profile/manage', + arguments: BusinessArguments( + widget.arguments.signedInUser, + widget.arguments.businessUser, + widget.arguments.business, + ), + ); + String message = "Your employees details have been updated."; + successPopUp(message); + } else { + internetConnectionPopUp(); + } + } + + Future deleteNoteApiCall(int index) async { + var response = await http.delete( + Uri.parse("$baseAPI/business-user/employees/delete/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({ + "business_id": widget.employees[index].business_id, + "app_id": widget.employees[index].app_id, + }), + ); + //print("Here4"); + //print(response.statusCode); + if (response.statusCode == 200) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pushNamed( + '/business-profile/manage', + arguments: BusinessArguments( + widget.arguments.signedInUser, + widget.arguments.businessUser, + widget.arguments.business, + ), + ); + String message = + "The employee has been deleted successfully. This means it will no longer have access to your business profile"; + successPopUp(message); + } else { + internetConnectionPopUp(); + } + } + + void internetConnectionPopUp() { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Internet Connection"); + }, + ); + } + + void successPopUp(String message) { + showDialog( + context: context, + builder: (context) { + return MIHSuccessMessage( + successType: "Success", + successMessage: message, + ); + }, + ); + } + + bool isRequiredFieldsCaptured() { + if (accessController.text.isEmpty || typeController.text.isEmpty) { + return false; + } else { + return true; + } + } + + void updateEmployeePopUp(int index) { + setState(() { + accessController.text = widget.employees[index].access; + typeController.text = widget.employees[index].title; + fnameController.text = widget.employees[index].fname; + lnameController.text = widget.employees[index].lname; + }); + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => MIHWindow( + fullscreen: false, + windowTitle: "Employee Details", + windowTools: [ + IconButton( + onPressed: () { + showDeleteWarning(index); + }, + icon: Icon( + Icons.delete, + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + size: 35, + ), + ), + ], + onWindowTapClose: () { + Navigator.pop(context); + }, + windowBody: [ + MIHTextField( + controller: fnameController, + hintText: "First Name", + editable: false, + required: true, + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: lnameController, + hintText: "Surname", + editable: false, + required: true, + ), + const SizedBox(height: 10.0), + MIHDropdownField( + controller: typeController, + hintText: "Title", + dropdownOptions: const ["Doctor", "Assistant"], + required: true, + editable: true, + enableSearch: false, + ), + const SizedBox(height: 10.0), + MIHDropdownField( + controller: accessController, + hintText: "Access", + dropdownOptions: const ["Full", "Partial"], + required: true, + editable: true, + enableSearch: false, + ), + const SizedBox(height: 15.0), + SizedBox( + width: 300, + height: 50, + child: MIHButton( + buttonText: "Update", + buttonColor: + MzanziInnovationHub.of(context)!.theme.secondaryColor(), + textColor: MzanziInnovationHub.of(context)!.theme.primaryColor(), + onTap: () { + if (isRequiredFieldsCaptured()) { + updateEmployeeAPICall(index); + } else { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Input Error"); + }, + ); + } + }, + ), + ) + ], + ), + ); + } + + void showDeleteWarning(int index) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => MIHDeleteMessage( + deleteType: "Employee", + onTap: () { + deleteNoteApiCall(index); + })); + } + + @override + void dispose() { + accessController.dispose(); + typeController.dispose(); + fnameController.dispose(); + lnameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + separatorBuilder: (BuildContext context, index) { + return Divider( + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ); + }, + itemCount: widget.employees.length, + itemBuilder: (context, index) { + //final patient = widget.patients[index].id_no.contains(widget.searchString); + //print(index); + var isMe = ""; + if (widget.arguments.signedInUser.app_id == + widget.employees[index].app_id) { + isMe = "(You)"; + } + return ListTile( + title: Text( + "${widget.employees[index].fname} ${widget.employees[index].lname} - ${widget.employees[index].title} $isMe"), + subtitle: Text( + "${widget.employees[index].username}\n${widget.employees[index].email}\nAccess: ${widget.employees[index].access}", + style: TextStyle( + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ), + onTap: () { + updateEmployeePopUp(index); + }, + ); + }, + ); + } +} diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/builders/build_user_list.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/builders/build_user_list.dart new file mode 100644 index 00000000..faffb8f4 --- /dev/null +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/builders/build_user_list.dart @@ -0,0 +1,237 @@ +import 'dart:convert'; + +import 'package:Mzansi_Innovation_Hub/main.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_button.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_dropdown_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_text_input.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_layout/mih_window.dart'; +import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_error_message.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_success_message.dart'; +import 'package:Mzansi_Innovation_Hub/mih_env/env.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/app_user.dart'; +import 'package:Mzansi_Innovation_Hub/mih_objects/arguments.dart'; +import 'package:flutter/material.dart'; +import 'package:supertokens_flutter/http.dart' as http; + +class BuildUserList extends StatefulWidget { + final List users; + final BusinessArguments arguments; + + const BuildUserList({ + super.key, + required this.users, + required this.arguments, + }); + + @override + State createState() => _BuildUserListState(); +} + +class _BuildUserListState extends State { + TextEditingController accessController = TextEditingController(); + TextEditingController typeController = TextEditingController(); + TextEditingController fnameController = TextEditingController(); + TextEditingController lnameController = TextEditingController(); + + final baseAPI = AppEnviroment.baseApiUrl; + + Future createBusinessUserAPICall(int index) async { + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + var response = await http.post( + Uri.parse("$baseAPI/business-user/insert/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + body: jsonEncode({ + "business_id": widget.arguments.business!.business_id, + "app_id": widget.users[index].app_id, + "signature": "", + "sig_path": "", + "title": typeController.text, + "access": accessController.text, + }), + ); + if (response.statusCode == 201) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pushNamed( + '/business-profile/manage', + arguments: BusinessArguments( + widget.arguments.signedInUser, + widget.arguments.businessUser, + widget.arguments.business, + ), + ); + String message = + "${widget.users[index].username} is now apart of your team with ${accessController.text} access to ${widget.arguments.business!.Name}"; + successPopUp(message); + } else { + internetConnectionPopUp(); + } + } + + bool isRequiredFieldsCaptured() { + if (accessController.text.isEmpty || typeController.text.isEmpty) { + return false; + } else { + return true; + } + } + + void internetConnectionPopUp() { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Internet Connection"); + }, + ); + } + + void successPopUp(String message) { + showDialog( + context: context, + builder: (context) { + return MIHSuccessMessage( + successType: "Success", + successMessage: message, + ); + }, + ); + } + + String hideEmail(String email) { + var firstLetter = email[0]; + var end = email.split("@")[1]; + return "$firstLetter********@$end"; + } + + void addEmployeePopUp(int index) { + setState(() { + //accessController.text = widget.users[index].access; + //typeController.text = widget.users[index].title; + // var fnameInitial = widget.users[index].fname[0]; + // var lnameInitial = widget.users[index].lname[0]; + fnameController.text = widget.users[index].username; + lnameController.text = hideEmail(widget.users[index].email); + }); + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => MIHWindow( + fullscreen: false, + windowTitle: "Add Employee", + windowBody: [ + const SizedBox(height: 10.0), + MIHTextField( + controller: fnameController, + hintText: "Username Name", + editable: false, + required: true, + ), + const SizedBox(height: 10.0), + MIHTextField( + controller: lnameController, + hintText: "Email", + editable: false, + required: true, + ), + const SizedBox(height: 10.0), + MIHDropdownField( + controller: typeController, + hintText: "Title", + dropdownOptions: const ["Doctor", "Assistant"], + required: true, + editable: true, + enableSearch: false, + ), + const SizedBox(height: 10.0), + MIHDropdownField( + controller: accessController, + hintText: "Access", + dropdownOptions: const ["Full", "Partial"], + required: true, + editable: true, + enableSearch: false, + ), + const SizedBox(height: 15.0), + SizedBox( + width: 300, + height: 50, + child: MIHButton( + buttonText: "Add", + buttonColor: + MzanziInnovationHub.of(context)!.theme.secondaryColor(), + textColor: + MzanziInnovationHub.of(context)!.theme.primaryColor(), + onTap: () { + if (isRequiredFieldsCaptured()) { + createBusinessUserAPICall(index); + } else { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage( + errorType: "Input Error"); + }, + ); + } + }, + ), + ), + const SizedBox(height: 10.0), + ], + windowTools: [], + onWindowTapClose: () { + Navigator.pop(context); + })); + } + + @override + void dispose() { + accessController.dispose(); + typeController.dispose(); + fnameController.dispose(); + lnameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + separatorBuilder: (BuildContext context, index) { + return Divider( + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ); + }, + itemCount: widget.users.length, + itemBuilder: (context, index) { + var isYou = ""; + if (widget.arguments.signedInUser.app_id == + widget.users[index].app_id) { + isYou = "(You)"; + } + return ListTile( + title: Text("@${widget.users[index].username} $isYou"), + subtitle: Text( + "Email: ${hideEmail(widget.users[index].email)}", + style: TextStyle( + color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), + ), + ), + onTap: () { + addEmployeePopUp(index); + }, + ); + }, + ); + } +} From 9839052ca042e7c6a94b79fe459c6bc74813c5ed Mon Sep 17 00:00:00 2001 From: yaso Date: Wed, 12 Mar 2025 13:47:58 +0200 Subject: [PATCH 4/4] update route --- Frontend/lib/mih_router/routeGenerator.dart | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/Frontend/lib/mih_router/routeGenerator.dart b/Frontend/lib/mih_router/routeGenerator.dart index c2e93195..1607893a 100644 --- a/Frontend/lib/mih_router/routeGenerator.dart +++ b/Frontend/lib/mih_router/routeGenerator.dart @@ -6,6 +6,7 @@ import 'package:Mzansi_Innovation_Hub/mih_packages/access_review/mih_access.dart import 'package:Mzansi_Innovation_Hub/mih_packages/calculator/mih_calculator.dart'; import 'package:Mzansi_Innovation_Hub/mih_packages/calendar/mzansi_calendar.dart'; import 'package:Mzansi_Innovation_Hub/mih_packages/mzansi_ai/mzansi_ai.dart'; +import 'package:Mzansi_Innovation_Hub/mih_packages/mzansi_profile/business_profile/mzansi_business_profile.dart'; import 'package:Mzansi_Innovation_Hub/mih_packages/mzansi_profile/personal_profile/mzansi_profile.dart'; import 'package:Mzansi_Innovation_Hub/mih_packages/mzansi_wallet/components/mih_barcode_scanner.dart'; import 'package:Mzansi_Innovation_Hub/mih_packages/mzansi_wallet/mih_wallet.dart'; @@ -17,17 +18,13 @@ import '../mih_packages/authentication/auth_check.dart'; import '../mih_packages/patient_profile/add_or_view_patient.dart'; import '../mih_packages/patient_profile/patient_add.dart'; import '../mih_packages/patient_profile/patient_edit.dart'; -// import '../mih_packages/patient_profile/patient_manager.dart'; import '../mih_objects/app_user.dart'; import '../mih_objects/arguments.dart'; -// import '../mih_packages/about_mih/mih_about.dart'; import '../mih_packages/authentication/forgot_password.dart'; import '../mih_packages/authentication/reset_password.dart'; import '../mih_packages/patient_profile/full_screen_file.dart'; -import '../mih_packages/manage_business/manage_business_profile.dart'; import '../mih_packages/patient_profile/patient_view.dart'; import '../mih_packages/manage_business/profile_business_add.dart'; -import '../mih_packages/manage_business/business_details.dart'; class RouteGenerator { static Route generateRoute(RouteSettings settings) { @@ -129,16 +126,6 @@ class RouteGenerator { //=============================================================== //Business Profile Pages - case '/business-profile': - if (args is BusinessArguments) { - return MaterialPageRoute( - settings: settings, - builder: (_) => BusinessDetails( - arguments: args, - ), - ); - } - return _errorRoute(); case '/business-profile/set-up': if (args is AppUser) { return MaterialPageRoute( @@ -153,9 +140,12 @@ class RouteGenerator { if (args is BusinessArguments) { return MaterialPageRoute( settings: settings, - builder: (_) => ManageBusinessProfile( + builder: (_) => MzansiBusinessProfile( arguments: args, ), + // ManageBusinessProfile( + // arguments: args, + // ), ); } return _errorRoute();