From a91166727a85048f8c028f4765f400ffbf5fb734 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Thu, 10 Apr 2025 16:55:45 +0530 Subject: [PATCH] policy ui/Functionily --- lib/Screens/group/group.dart | 627 ++++++++++++++++++ lib/Screens/group/groupList.dart | 192 ++++++ lib/Screens/organization/mailSettings.dart | 552 +++++++++++++++ lib/Screens/organization/orgSetup.dart | 483 ++++++++++++++ lib/Screens/organization/themeColor.dart | 124 ++++ lib/routes/custom_drawer.dart | 4 +- lib/routes/custom_router.dart | 19 + lib/services/apiService.dart | 142 ++++ lib/utils/auth_utils.dart | 31 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 104 +++ pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 16 files changed, 2288 insertions(+), 2 deletions(-) create mode 100644 lib/Screens/group/group.dart create mode 100644 lib/Screens/group/groupList.dart create mode 100644 lib/Screens/organization/mailSettings.dart create mode 100644 lib/Screens/organization/orgSetup.dart create mode 100644 lib/Screens/organization/themeColor.dart diff --git a/lib/Screens/group/group.dart b/lib/Screens/group/group.dart new file mode 100644 index 0000000..b67cf30 --- /dev/null +++ b/lib/Screens/group/group.dart @@ -0,0 +1,627 @@ +import 'dart:convert'; +import 'dart:core'; +import 'dart:io'; +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:frontend/Screens/organization/mailSettings.dart'; +import 'package:frontend/Screens/organization/themeColor.dart'; +import 'package:frontend/utils/auth_utils.dart'; +import 'package:go_router/go_router.dart'; +import 'package:http/http.dart' as http; +import 'package:image_picker/image_picker.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../../config/apiUrl.dart'; +import '../../routes/custom_appBar.dart'; +import '../../routes/custom_drawer.dart'; +import '../../services/apiService.dart'; +import '../../widgets/custom_text_field.dart'; +import '../../widgets/custom_user_form.dart'; +import 'groupList.dart'; + +class Group extends StatefulWidget { + final Map? group; + + const Group({Key? key, required this.group}) : super(key: key); + + static Group fromState(GoRouterState state) { + return Group(group: state.extra as Map?); + } + + @override + _groupState createState() => _groupState(); +} + +class _groupState extends State { + final ApiService apiService = ApiService(); + + String? orgId; + String? userId; + String? selectedGroupId; + + String? selectedDomestic; + String? selectedInternational; + + List? apiAllPolicy; + List? apiForDomestic; + List? apiForInternational; + Map errorMessages = {}; + final Map controllers = {}; + + List dataHeader = ["name", "description"]; + + Map get groupData { + final data = { + "name": controllers["name"]?.text, + "description": controllers["description"]?.text, + "domestic_policy_id": selectedDomestic, + "international_policy_id": selectedInternational, + "org_id": orgId, + "created_by": userId, + }; + + // Only add group_id if it's an edit operation + if (widget.group != null && widget.group!.containsKey('group_id')) { + data["group_id"] = selectedGroupId; + } + + return data; + } + + @override + void initState() { + super.initState(); + loadAllServices(); + + for (var field in dataHeader) { + controllers[field] = TextEditingController(); + } + + updateData(); + } + + Future updateData() async { + // Ensure apiselectedUser is not null before printing + if (widget.group != null) { + print("API Selected User Has Data - ${widget.group}"); + setState(() { + // ✅ Wrap in setState to update the UI + selectedGroupId = widget.group?["group_id"] ?? ""; + controllers["name"]?.text = widget.group?["name"] ?? ""; + controllers["description"]?.text = widget.group?["description"] ?? ""; + + if (widget.group?["domestic_policy_id"] != null) { + selectedDomestic = widget.group!["domestic_policy_id"].toString(); + } + + if (widget.group?["international_policy_id"] != null) { + selectedInternational = + widget.group!["international_policy_id"].toString(); + } + }); + } else { + print("API Selected User Has Data - No data available yet"); + } + } + + Future loadAllServices() async { + try { + final result = await apiService.fetchAllPolicy(); + + orgId = await getOrgId(); + userId = await getUserId(); + + setState(() { + apiAllPolicy = result; + + apiForDomestic = + result.where((policy) => policy["domestic"] == "1").toList(); + apiForInternational = + result.where((policy) => policy["international"] == "1").toList(); + }); + print("Fetched services: $apiAllPolicy"); + print("Domestic policies: $apiForDomestic"); + print("International policies: $apiForInternational"); + } catch (e) { + print('Error fetching role list: $e'); + } + } + + bool isValidData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List requiredFields = [ + "name", + "description", + ]; + + // Check validation for each field + for (String field in requiredFields) { + if (data[field] == null || data[field].toString().trim().isEmpty) { + errorMessages[field] = "Required"; + } + } + + return errorMessages.isEmpty; // Valid if there are no errors + } + + void _clearError(String field) { + if (mounted && errorMessages.containsKey(field)) { + setState(() { + errorMessages.remove(field); + }); + } + } + + void handleSubmit() { + print("HandleSubmiy - $groupData"); + + setState(() { + if (!isValidData(groupData)) { + print("USERDETAILS : $groupData"); + print("Validation Failed: Required fields are missing."); + return; // Stop execution if validation fails + } else { + print("USERDETAILS : $groupData"); + postGroupData(groupData); + } + }); + } + + Future postGroupData(Map groupData) async { + // final String apiUrldata = '$apiUrl/api/groups/create'; + + final token = await getToken(); // Fetch token + + print("postGroupData- ${widget.group?['group_id']}"); + + // Determine whether it's create or update + final bool isEdit = widget.group != null && widget.group!.isNotEmpty; + final int? groupId = int.tryParse(selectedGroupId!); + + print("postGroupData1 - $selectedGroupId"); + + // Build correct API URL + final String apiUrlData = isEdit + ? '$apiUrl/api/groups/update/$groupId' // Update API + : '$apiUrl/api/groups/create'; + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + try { + final response = await (isEdit + ? http.put( + // <-- Use PUT for update + Uri.parse(apiUrlData), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + body: jsonEncode(groupData), + ) + : http.post( + Uri.parse(apiUrlData), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + body: jsonEncode(groupData), // Convert map to JSON + )); + + if (response.statusCode == 200 || response.statusCode == 201) { + print("Plan submitted successfully!"); + print("Response: ${response.body}"); + + context.go('/group'); + } else { + print("Failed to submit plan. Status: ${response.statusCode}"); + print("Error: ${response.body}"); + } + } catch (e) { + print(" Error submitting plan: $e"); + } + } + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder(builder: (context, sizingInfo) { + bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + + return Scaffold( + backgroundColor: Colors.white, + appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), + drawer: isDesktop ? null : CustomDrawer(isDesktop: false), + body: Row( + children: [ + if (isDesktop) CustomDrawer(isDesktop: true), + Expanded(child: buildOrganizationLayout(isDesktop)) + ], + ), + ); + }); + } + + Widget buildOrganizationLayout(isDesktop) { + return Container( + color: Colors.white, + width: double.infinity, + height: MediaQuery.of(context).size.height, + margin: const EdgeInsets.all(8), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Create Group", + style: TextStyle(fontSize: 18), + ), + Container( + color: Color(0xFFE9EBF6), + child: IconButton( + icon: Icon(Icons.close), + onPressed: () { + context.go('/group'); + }, + ), + ) + ], + ), + ), + SizedBox( + height: 30, + ), + Container( + padding: const EdgeInsets.all(20), + color: Colors.grey.shade100, + child: Column( + children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text( + // "Mail Settings", + // style: TextStyle(color: Colors.blueAccent), + // ), + // Icon( + // Icons.keyboard_arrow_down_outlined, + // color: Colors.blueAccent, + // size: 30, + // ), + // ], + // ), + isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildFirstRow(isDesktop), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildFirstRow(isDesktop), + ), + + SizedBox( + height: 10, + ), + + isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildSecondRow(isDesktop), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildSecondRow(isDesktop), + ), + + SizedBox(height: 15), + isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: _buildSubmit(isDesktop), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: _buildSubmit(isDesktop), + ) + ], + )), + ], + ), + ), + ); + } + + List _buildFirstRow(bool isDesktop) { + return [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Group Name", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: isDesktop, + width: isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["name"], + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + labelText: "group name", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["name"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["name"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Description", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: isDesktop, + width: isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["description"], + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + labelText: "description", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["description"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["description"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + ]; + } + + List _buildSecondRow(bool isDesktop) { + return [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Select Policy For Domestic", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: isDesktop, + width: isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 40, + child: apiForDomestic == null + ? Center( + child: Transform.scale( + scale: 0.5, + child: CircularProgressIndicator(), + ), + ) + : DropdownSearch( + selectedItem: selectedDomestic == null + ? null + : apiForDomestic! + .firstWhere((policy) => + policy['policy_id'] == + selectedDomestic)['name'] + .toString(), + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search Policy...", + contentPadding: + EdgeInsets.symmetric(horizontal: 10), + ), + ), + ), + items: apiForDomestic! + .map((policy) => policy['name'].toString()) + .toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + selectedDomestic = apiForDomestic!.firstWhere( + (policy) => + policy['name'] == newValue)['policy_id']; + }); + }, + ), + ), + ) + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Select Policy For International", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: isDesktop, + width: isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 40, + child: apiForInternational == null + ? Center( + child: Transform.scale( + scale: 0.5, + child: CircularProgressIndicator(), + ), + ) + : DropdownSearch( + selectedItem: selectedInternational == null + ? null + : apiForInternational! + .firstWhere((policy) => + policy['policy_id'] == + selectedInternational)['name'] + .toString(), + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search Policy...", + contentPadding: + EdgeInsets.symmetric(horizontal: 10), + ), + ), + ), + items: apiForInternational! + .map((policy) => policy['name'].toString()) + .toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + selectedInternational = apiForInternational! + .firstWhere((policy) => + policy['name'] == newValue)['policy_id']; + }); + }, + ), + ), + ) + ], + ), + ]; + } + + List _buildSubmit(isDesktop) { + return [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.blueAccent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: Colors.blueAccent, width: 2), + ), + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + ), + onPressed: () { + context.go('/group'); + }, + child: Text("Cancel")), + SizedBox( + width: 20, + ), + MouseRegion( + // cursor: widget.isViewMode + // ? SystemMouseCursors.forbidden + // : SystemMouseCursors.click, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueAccent, // Keep original color + foregroundColor: Colors.white, // Keep original color + disabledBackgroundColor: + Colors.blueAccent, // Ensure color remains when disabled + disabledForegroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: Colors.blueAccent, width: 2), + ), + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + ), + onPressed: handleSubmit, // Disable when in view mode + child: Text("Submit"), + ), + ) + ]; + } +} diff --git a/lib/Screens/group/groupList.dart b/lib/Screens/group/groupList.dart new file mode 100644 index 0000000..02d1696 --- /dev/null +++ b/lib/Screens/group/groupList.dart @@ -0,0 +1,192 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/Screens/group/group.dart'; +import 'package:go_router/go_router.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../../routes/custom_appBar.dart'; +import '../../routes/custom_drawer.dart'; +import '../../services/apiService.dart'; + +class GroupList extends StatefulWidget { + @override + _GroupListState createState() => _GroupListState(); +} + +class _GroupListState extends State { + final ApiService apiService = ApiService(); + + List? apiAllGroups; + + @override + void initState() { + super.initState(); + loadAllGroups(); + } + + Future loadAllGroups() async { + try { + final result = await apiService.fetchAllGroup(); + setState(() { + apiAllGroups = result; + }); + print("Fetched services: $apiAllGroups"); + } catch (e) { + print('Error fetching role list: $e'); + } + } + + void deleteGroup(int groupId) { + setState(() { + apiAllGroups?.removeWhere((group) => group['group_id'] == groupId); + }); + } + + // Future deleteGroupFromApi(int groupId) async { + // try { + // await apiService.deleteGroup(groupId); // your delete API call + // deleteGroup(groupId); // remove from UI list + // } catch (e) { + // print('Error deleting group: $e'); + // } + // }' + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder(builder: (context, sizingInfo) { + bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + + return Scaffold( + backgroundColor: Colors.white, + appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), + drawer: isDesktop ? null : CustomDrawer(isDesktop: false), + body: Row( + children: [ + if (isDesktop) CustomDrawer(isDesktop: true), + Expanded(child: buildGroupListLayout(isDesktop)) + ], + ), + ); + }); + } + + Widget buildGroupListLayout(bool isDesktop) { + return Container( + color: Colors.white, + width: double.infinity, + height: MediaQuery.of(context).size.height, + margin: const EdgeInsets.all(8), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const Text('Group List', + style: + TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + IconButton( + icon: const Icon(Icons.keyboard_arrow_down), + onPressed: () {}, + ), + ], + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.blueAccent), + onPressed: () async { + // List users = await futureUsers; + context.go('/CreateGroup'); + }, + child: Row( + children: [ + Icon( + Icons.add_circle, + color: Colors.white, + ), + SizedBox( + width: 5, + ), + Text('New Group'), + ], + ), + ), + ], + ), + Row( + children: [ + Expanded( + child: Container( + height: MediaQuery.of(context).size.height * 0.88, + margin: const EdgeInsets.only(bottom: 10), + // color: Colors.red.shade100, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + children: [ + buildGroupListView(isDesktop), + ], + ), + ), + ), + ), + ], + ) + ], + ), + ); + } + + // Widget buildGroupListView(bool isDesktop) { + // return Container( + // child: Text("DAta"), + // ); + // } + + Widget buildGroupListView(bool isDesktop) { + if (apiAllGroups == null || apiAllGroups!.isEmpty) { + return Center(child: Text("No groups found.")); + } + + return ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: apiAllGroups!.length, + itemBuilder: (context, index) { + final group = apiAllGroups![index]; + return Card( + margin: EdgeInsets.symmetric(vertical: 6, horizontal: 10), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Group Name: ${group['name']}", + style: + TextStyle(fontSize: 13, fontWeight: FontWeight.bold)), + SizedBox(height: 4), + Text("Description: ${group['description'] ?? 'N/A'}"), + Text("Policy: ${group['domestic_policy_id']}"), + Text("Created By : ${group['created_by']}"), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + context.go("/CreateGroup", extra: group); + + print("Edit ${group['group_id']} $group"); + }, + child: Text("Edit"), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/Screens/organization/mailSettings.dart b/lib/Screens/organization/mailSettings.dart new file mode 100644 index 0000000..fa86cef --- /dev/null +++ b/lib/Screens/organization/mailSettings.dart @@ -0,0 +1,552 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; + +import '../../config/apiUrl.dart'; +import '../../utils/auth_utils.dart'; +import '../../widgets/custom_text_field.dart'; + +class MailSetting extends StatefulWidget { + bool isDesktop; + final Function(Map) onMailDataChanged; + + MailSetting({ + super.key, + required this.isDesktop, + required this.onMailDataChanged, + }); + + @override + _MailSettingState createState() => _MailSettingState(); +} + +class _MailSettingState extends State { + String? orgId; + String? userId; + + Map errorMessages = {}; + final Map controllers = {}; + + List dataHeader = [ + "host", + "userName", + "password", + "port", + "senderEmail", + "toEmail" + ]; + + Map getMailData() => mailData; + + Map get mailData { + final data = { + "mail_host": controllers["host"]?.text, + "mail_user_name": controllers["userName"]?.text, + "mail_password": controllers["password"]?.text, + "mail_port": controllers["port"]?.text, + "sender_email": controllers["senderEmail"]?.text, + "to_mail": controllers["toEmail"]?.text, + + // "org_id": orgId, + // "created_by": userId, + }; + + // Only add group_id if it's an edit operation + // if (widget.group != null && widget.group!.containsKey('group_id')) { + // data["group_id"] = selectedGroupId; + // } + + return data; + } + + void _initControllers() { + for (var field in dataHeader) { + controllers[field] = TextEditingController(); + controllers[field]!.addListener(() { + widget.onMailDataChanged(mailData); // Notify parent + }); + } + } + + @override + void initState() { + super.initState(); + // loadAllServices(); + + for (var field in dataHeader) { + controllers[field] = TextEditingController(); + } + + _initControllers(); + } + + void handleTestMailSubmit() { + print("TEStMailData- $mailData"); + + if (!isValidData(mailData)) { + print("USERDETAILS : $mailData"); + print("Validation Failed: Required fields are missing."); + setState(() {}); + return; // Stop execution if validation fails + } else { + print("MailSuccessDETAILS : $mailData"); + // orgId = await getOrgId(); + + sendTestMail(mailData); + } + } + + Future sendTestMail(Map mailData) async { + // final String apiUrldata = '$apiUrl/api/groups/create'; + + final token = await getToken(); // Fetch token + + // Build correct API URL + final String apiUrlData = '$apiUrl/api/organizations/testMail'; + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + try { + final response = await http.post( + Uri.parse(apiUrlData), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + body: jsonEncode(mailData), // Convert map to JSON + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + print("Plan submitted successfully!"); + print("Response: ${response.body}"); + } else { + print("Failed to submit plan. Status: ${response.statusCode}"); + print("Error: ${response.body}"); + } + } catch (e) { + print(" Error submitting plan: $e"); + } + } + + bool isValidData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List requiredFields = [ + "mail_host", + "mail_user_name", + "mail_password", + "mail_port", + "sender_email", + "to_mail", + ]; + + // if (apiselectedUser == null) { + // requiredFields.add("password"); + // } + + // Check validation for each field + for (String field in requiredFields) { + if (data[field] == null || data[field].toString().trim().isEmpty) { + errorMessages[field] = "Required"; + } + } + + // Email validation + if (data["to_mail"] != null && data["to_mail"].toString().isNotEmpty) { + if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") + .hasMatch(data["to_mail"].toString())) { + errorMessages["to_mail"] = + "Invalid email format"; // Invalid email format + } + } + + // Email validation + if (data["sender_email"] != null && + data["sender_email"].toString().isNotEmpty) { + if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") + .hasMatch(data["sender_email"].toString())) { + errorMessages["sender_email"] = + "Invalid email format"; // Invalid email format + } + } + return errorMessages.isEmpty; // Valid if there are no errors + } + + void _clearError(String field) { + if (mounted && errorMessages.containsKey(field)) { + setState(() { + errorMessages.remove(field); + }); + } + } + + @override + Widget build(BuildContext context) { + return Expanded( + child: Container( + padding: const EdgeInsets.all(8.0), + // color: Colors.amber.shade100, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Text("mail Settings data"), + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Sender Email", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 40, + child: TextField( + // focusNode: _destinationFocusNode, + controller: controllers["senderEmail"], + onChanged: (value) { + _clearError("sender_email"); + }, + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + labelText: "sender email", + labelStyle: + TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["sender_email"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["sender_email"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + ], + ), + SizedBox( + height: 10, + ), + + widget.isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildMailFirstRow(widget.isDesktop), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildMailFirstRow(widget.isDesktop), + ), + + SizedBox( + height: 10, + ), + + widget.isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildMailSecondRow(widget.isDesktop), + ) + : Column( + children: _buildMailSecondRow(widget.isDesktop), + ), + + SizedBox( + height: 10, + ), + + Container( + // color: Color(0xFFF7F7FB), + padding: const EdgeInsets.only(top: 5, bottom: 5), + child: Row( + children: [ + Text( + "Test Mail", + style: TextStyle(color: Colors.blueAccent), + ), + ], + ), + ), + + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + children: _buildTestMail(widget.isDesktop), + ) + ], + ), + ), + ); + } + + List _buildMailFirstRow(bool isDesktop) { + return [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "User Name", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 40, + child: TextField( + // focusNode: _destinationFocusNode, + controller: controllers["userName"], + onChanged: (value) { + _clearError("mail_user_name"); + }, + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + labelText: "user name", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["mail_user_name"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["mail_user_name"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Password", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 40, + child: TextField( + // focusNode: _destinationFocusNode, + controller: controllers["password"], + onChanged: (value) { + _clearError("mail_password"); + }, + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + labelText: "password", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["mail_password"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["mail_password"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + ]; + } + + List _buildMailSecondRow(bool isDesktop) { + return [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Host", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 40, + child: TextField( + // focusNode: _destinationFocusNode, + controller: controllers["host"], + onChanged: (value) { + _clearError("mail_host"); + }, + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + labelText: "host", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["mail_host"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["mail_host"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Port", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 40, + child: TextField( + // focusNode: _destinationFocusNode, + controller: controllers["port"], + onChanged: (value) { + _clearError("mail_port"); + }, + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + labelText: "port", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["mail_port"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["mail_port"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + ]; + } + + List _buildTestMail(bool isDesktop) { + return [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Enter Your Mail Id", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 40, + child: TextField( + // focusNode: _destinationFocusNode, + controller: controllers["toEmail"], + onChanged: (value) { + _clearError("to_mail"); + }, + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + labelText: "To mail", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["to_mail"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["to_mail"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + width: 10, + ), + Column( + children: [ + ElevatedButton( + onPressed: () { + handleTestMailSubmit(); + }, + child: Text("Test Email")) + ], + ), + ]; + } +} diff --git a/lib/Screens/organization/orgSetup.dart b/lib/Screens/organization/orgSetup.dart new file mode 100644 index 0000000..2494498 --- /dev/null +++ b/lib/Screens/organization/orgSetup.dart @@ -0,0 +1,483 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:frontend/Screens/organization/mailSettings.dart'; +import 'package:frontend/Screens/organization/themeColor.dart'; +import 'package:go_router/go_router.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../../routes/custom_appBar.dart'; +import '../../routes/custom_drawer.dart'; +import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; + +class OrgSetUp extends StatefulWidget { + @override + _OrgSetUpState createState() => _OrgSetUpState(); +} + +class _OrgSetUpState extends State { + final ApiService apiService = ApiService(); + Map errorMessages = {}; + final Map controllers = {}; + + List? apiAllServices; + List? selectedService; + + bool isSelected = false; + + @override + void initState() { + super.initState(); + loadAllServices(); + getUpdatedServices(); + } + + Future loadAllServices() async { + try { + final result = await apiService.fetchAllServices(); + setState(() { + apiAllServices = result; + }); + print("Fetched services: $apiAllServices"); + } catch (e) { + print('Error fetching role list: $e'); + } + } + + Future getUpdatedServices() async { + try { + final result = await apiService.fetchUpdatedOrganization(); + + setState(() { + selectedService = result; + }); + + print("UUPdatedServices - $selectedService"); + } catch (e) { + print('Error fetching role list: $e'); + } + } + + bool isValidData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List requiredFields = [ + "name", + "description", + ]; + + // Check validation for each field + for (String field in requiredFields) { + if (data[field] == null || data[field].toString().trim().isEmpty) { + errorMessages[field] = "Required"; + } + } + + return errorMessages.isEmpty; // Valid if there are no errors + } + + void _clearError(String field) { + if (mounted && errorMessages.containsKey(field)) { + setState(() { + errorMessages.remove(field); + }); + } + } + + void handleSubmit() { + // print("HandleSubmiy - $groupData"); + + // setState(() { + // if (!isValidData(groupData)) { + // print("USERDETAILS : $groupData"); + // print("Validation Failed: Required fields are missing."); + // return; // Stop execution if validation fails + // } else { + // print("USERDETAILS : $groupData"); + // postGroupData(groupData); + // } + // }); + } + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder(builder: (context, sizingInfo) { + bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + + return Scaffold( + backgroundColor: Colors.white, + appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), + drawer: isDesktop ? null : CustomDrawer(isDesktop: false), + body: Row( + children: [ + if (isDesktop) CustomDrawer(isDesktop: true), + Expanded(child: buildOrganizationLayout(isDesktop)) + ], + ), + ); + }); + } + + Widget buildOrganizationLayout(isDesktop) { + File? _imageFile; + Uint8List? _webImage; + + Color? layoutColor; + Color? bodyColor; + + Future _pickImage() async { + final pickedFile = + await ImagePicker().pickImage(source: ImageSource.gallery); + + if (pickedFile != null) { + if (kIsWeb) { + final bytes = await pickedFile.readAsBytes(); // <-- key part + setState(() { + _webImage = bytes; + }); + } else { + setState(() { + _imageFile = File(pickedFile.path); + }); + } + } + } + + return Container( + color: Colors.white, + width: double.infinity, + height: MediaQuery.of(context).size.height, + margin: const EdgeInsets.all(8), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Create Organization", + style: TextStyle(fontSize: 18), + ), + ], + ), + ), + Container( + color: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Name:", + style: TextStyle(fontSize: 16, color: Colors.black), + ), + SizedBox(width: 8), + Expanded( + child: TextField( + // controller: _visaCommentsController, + style: TextStyle(fontSize: 16, color: Colors.blueAccent), + decoration: InputDecoration( + labelText: "Enter Organization Name", + labelStyle: TextStyle(fontSize: 16, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 4), + ), + ), + ), + Spacer(), + GestureDetector( + onTap: _pickImage, + child: CircleAvatar( + radius: 30, + backgroundColor: Colors.amber, + child: ClipOval( + child: _webImage != null + ? Image.memory(_webImage!, + width: 50, height: 50, fit: BoxFit.cover) + : _imageFile != null + ? Image.file(_imageFile!, + width: 50, height: 50, fit: BoxFit.cover) + : Icon(Icons.camera_alt, + size: 18, color: Colors.white), + ), + ), + ), + ], + ), + ), + SizedBox( + height: 15, + ), + Container( + color: Colors.white, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Mail Settings", + style: TextStyle(color: Colors.blueAccent), + ), + Icon( + Icons.keyboard_arrow_down_outlined, + color: Colors.blueAccent, + size: 30, + ), + ], + ), + Container( + // width: double.infinity, + decoration: BoxDecoration( + border: Border.all( + color: Colors.blueGrey.shade100, + width: 1.0, + // color: Colors.blueAccent + )), + child: Row( + mainAxisAlignment: isDesktop + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: [ + MailSetting( + isDesktop: isDesktop, + onMailDataChanged: (updatedData) { + // You can setState here or do something else with updatedData + // print("Updated Mail Data: $updatedData"); + }, + ), + ], + )) + ], + )), + SizedBox( + height: 10, + ), + Text( + "Services", + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + SizedBox( + height: 10, + ), + Container( + decoration: BoxDecoration( + border: Border.all(color: Color(0xFFF4F4FB)), + borderRadius: BorderRadius.circular(1), + color: Color(0xFFF4F4FB), + ), + padding: EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 15), + child: isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // mainAxisSize: MainAxisSize.min, + children: _buildOptions(), + ) + : Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: _buildOptions(), + ), + ), + ), + ), + SizedBox( + height: 10, + ), + Text( + "Choose Theme", + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + Container( + decoration: BoxDecoration( + // border: Border.all(color: Color(0xFFF4F4FB)), + borderRadius: BorderRadius.circular(1), + // color: Color(0xFFF4F4FB), + ), + padding: EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 15), + child: ColorThemePickerWidget( + onLayoutColorSelected: (Color selectedLayoutColor) { + setState(() { + layoutColor = selectedLayoutColor; + }); + }, + onBodyColorSelected: (Color selectedBodyColor) { + setState(() { + bodyColor = selectedBodyColor; + }); + }, + ), + ), + isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: _buildSubmit(isDesktop), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: _buildSubmit(isDesktop), + ) + ], + ), + ), + ); + } + + List _buildOptions() { + if (apiAllServices == null) return []; + + return apiAllServices!.map((service) { + return Padding( + padding: const EdgeInsets.only(right: 20.0), + child: _buildOption(service), + ); + }).toList(); + } + + Widget _buildOption(Map service) { + String name = service['name']; + String iconUrl = service['icon']; // Can be empty string + // Optional: define local icon fallback if iconUrl is empty + IconData fallbackIcon = _getLocalIconForService(name); + + return GestureDetector( + onTap: () { + setState(() { + // selectedListOption = title; + // isSelected = false; + }); + }, + child: Row(children: [ + iconUrl.isNotEmpty + ? Image.network( + iconUrl, + width: 18, + height: 18, + errorBuilder: (context, error, stackTrace) { + return Icon(fallbackIcon, size: 18, color: Colors.blueAccent); + }, + ) + : Icon(fallbackIcon, size: 18, color: Colors.blueAccent), + + SizedBox(width: 5), + + SizedBox(width: 5), + Text( + name, + style: TextStyle( + fontSize: 13, + // color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74), + color: Colors.grey, + fontWeight: FontWeight.bold), + // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), + ), + + SizedBox(width: 5), + // if (selectedListOption == title && widget.isViewMode == false) + GestureDetector( + onTap: () { + setState(() { + isSelected = !isSelected; + }); + }, + child: Container( + height: 15, + width: 15, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected ? Colors.green : Colors.grey, width: 1), + ), + child: Icon( + Icons.check_circle, + size: 10, + color: isSelected ? Colors.green : Colors.grey, + // color: Colors.grey, + )), + ), + ]), + ); + } + + IconData _getLocalIconForService(String name) { + switch (name.toLowerCase()) { + case 'flight': + return Icons.flight; + case 'train': + return Icons.train; + case 'bus': + return Icons.directions_bus; + case 'taxi': + return Icons.local_taxi; + case 'accomodation': + return Icons.hotel; + case 'forex': + return Icons.attach_money; + case 'insurance': + return Icons.verified_user; + case 'visa': + return Icons.badge; + case 'miscellaneous': + return Icons.widgets; + default: + return Icons.circle_notifications; + } + } + + List _buildSubmit(isDesktop) { + return [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.blueAccent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: Colors.blueAccent, width: 2), + ), + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + ), + onPressed: () { + context.go('/group'); + }, + child: Text("Cancel")), + SizedBox( + width: 20, + ), + MouseRegion( + // cursor: widget.isViewMode + // ? SystemMouseCursors.forbidden + // : SystemMouseCursors.click, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueAccent, // Keep original color + foregroundColor: Colors.white, // Keep original color + disabledBackgroundColor: + Colors.blueAccent, // Ensure color remains when disabled + disabledForegroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: Colors.blueAccent, width: 2), + ), + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + ), + onPressed: handleSubmit, // Disable when in view mode + child: Text("Submit"), + ), + ) + ]; + } +} diff --git a/lib/Screens/organization/themeColor.dart b/lib/Screens/organization/themeColor.dart new file mode 100644 index 0000000..6e1454a --- /dev/null +++ b/lib/Screens/organization/themeColor.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; + +class ColorThemePickerWidget extends StatefulWidget { + final Function(Color) onLayoutColorSelected; + final Function(Color) onBodyColorSelected; + + const ColorThemePickerWidget({ + Key? key, + required this.onLayoutColorSelected, + required this.onBodyColorSelected, + }) : super(key: key); + + @override + State createState() => _ColorThemePickerWidgetState(); +} + +class _ColorThemePickerWidgetState extends State { + Color? selectedLayoutColor; + Color? selectedBodyColor; + + // Layout colors + final List layoutThemeColors = [ + Color(0xFF448AFF), // BlueAccent + Color(0xFFF44336), // Red + Color(0xFF4CAF50), // Green + Color(0xFFFF9800), // Orange + Color(0xFF9C27B0), // Purple + ]; + + // Body colors + final List bodyThemeColors = [ + Colors.grey, + Colors.grey.shade300, + Colors.blue.shade50, + Colors.grey.shade100, + Colors.blueGrey.shade50, + ]; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + // Layout Color Picker + GestureDetector( + onTap: () => _showColorPickerDialog( + title: "Choose Layout Color", + colors: layoutThemeColors, + onColorSelected: (color) { + setState(() { + selectedLayoutColor = color; + }); + widget.onLayoutColorSelected(color); + }, + ), + child: _buildColorBox( + selectedLayoutColor ?? Colors.grey.shade300, Icons.palette), + ), + SizedBox(width: 15), + // Body Color Picker + GestureDetector( + onTap: () => _showColorPickerDialog( + title: "Choose Body Color", + colors: bodyThemeColors, + onColorSelected: (color) { + setState(() { + selectedBodyColor = color.withOpacity(0.3); // low opacity + }); + widget.onBodyColorSelected(selectedBodyColor!); + }, + ), + child: _buildColorBox( + selectedBodyColor ?? Colors.grey.shade300, Icons.opacity), + ), + ], + ); + } + + Widget _buildColorBox(Color color, IconData icon) { + return Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: color, + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(5), + ), + child: Icon(icon, color: Colors.white, size: 20), + ); + } + + void _showColorPickerDialog({ + required String title, + required List colors, + required Function(Color) onColorSelected, + }) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: Wrap( + spacing: 10, + runSpacing: 10, + children: colors.map((color) { + return GestureDetector( + onTap: () { + onColorSelected(color); + Navigator.of(context).pop(); + }, + child: Container( + width: 30, + height: 30, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + border: Border.all(color: Colors.black26), + ), + ), + ); + }).toList(), + ), + ), + ); + } +} diff --git a/lib/routes/custom_drawer.dart b/lib/routes/custom_drawer.dart index 903a15c..0480d79 100644 --- a/lib/routes/custom_drawer.dart +++ b/lib/routes/custom_drawer.dart @@ -144,7 +144,9 @@ class _CustomDrawerState extends State { _buildSubDrawerItem(context, 'User List', '/listUser'), // _buildSubDrawerItem(context,'PlanB','/PlanB') ]), - _buildExpandableItem(context, Icons.policy, 'Policy ', [ + _buildExpandableItem(context, Icons.policy, 'Settings ', [ + _buildSubDrawerItem(context, 'Organization', '/OrganizationSetup'), + _buildSubDrawerItem(context, 'Group', '/group'), _buildSubDrawerItem(context, 'Policy', '/Policy'), // _buildSubDrawerItem(context,'PlanB','/PlanB') ]), diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index 91175a8..9631930 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -1,8 +1,10 @@ import 'dart:convert'; +import 'package:flutter/material.dart'; import 'package:frontend/Screens/authentication/login/login_page.dart'; import 'package:frontend/Screens/authentication/loginPage1.dart'; import 'package:frontend/Screens/dashboard/home_page.dart'; +import 'package:frontend/Screens/organization/orgSetup.dart'; import 'package:frontend/Screens/plans/create_plans.dart'; import 'package:frontend/Screens/plans/list_plans.dart'; import 'package:frontend/Screens/policy/policy.dart'; @@ -10,6 +12,9 @@ import 'package:frontend/Screens/userManagement/create_user/create_user.dart'; import 'package:frontend/Screens/userManagement/user_List.dart'; import 'package:go_router/go_router.dart'; +import '../Screens/group/group.dart'; +import '../Screens/group/groupList.dart'; + final GoRouter router = GoRouter( routes: [ GoRoute( @@ -57,5 +62,19 @@ final GoRouter router = GoRouter( path: '/Policy', builder: (context, state) => Policy(), ), + GoRoute( + path: '/OrganizationSetup', + builder: (context, state) => OrgSetUp(), + ), + GoRoute( + path: '/group', + builder: (context, state) => GroupList(), + ), + GoRoute( + path: '/CreateGroup', + pageBuilder: (context, state) => MaterialPage( + child: Group.fromState(state), + ), + ), ], ); diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 1d12d96..b990787 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -158,4 +158,146 @@ class ApiService { throw Exception('Failed to load plans'); } } + + Future> fetchAllServices() async { + final String apiUrldata = '$apiUrl/api/service'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + print(data); + if (!data.containsKey('data') || data['data'] is! List) { + throw Exception( + "Invalid response format: 'data' field is missing or not a List"); + } + return data['data']; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load plans'); + } + } + + Future> fetchAllGroup() async { + String? orgId = await getOrgId(); + + final String apiUrldata = '$apiUrl/api/groups?org_id=$orgId'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + print(data); + if (!data.containsKey('data') || data['data'] is! List) { + throw Exception( + "Invalid response format: 'data' field is missing or not a List"); + } + return data['data']; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load plans'); + } + } + + Future> fetchAllPolicy() async { + String? orgId = await getOrgId(); + + final String apiUrldata = '$apiUrl/api/policy?org_id=$orgId'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + print(data); + if (!data.containsKey('data') || data['data'] is! List) { + throw Exception( + "Invalid response format: 'data' field is missing or not a List"); + } + return data['data']; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load plans'); + } + } + + Future> fetchUpdatedOrganization() async { + String? orgId = await getOrgId(); + + final String apiUrldata = '$apiUrl/api/organizations/find/$orgId'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + print(data); + if (!data.containsKey('data') || data['data'] is! List) { + throw Exception( + "Invalid response format: 'data' field is missing or not a List"); + } + return data['data']; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load plans'); + } + } } diff --git a/lib/utils/auth_utils.dart b/lib/utils/auth_utils.dart index 297b6ac..545a682 100644 --- a/lib/utils/auth_utils.dart +++ b/lib/utils/auth_utils.dart @@ -9,7 +9,17 @@ Future getToken() async { Future getUserId() async { final prefs = await SharedPreferences.getInstance(); - return prefs.getString('userId'); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["user_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; } Future getOrgId() async { @@ -26,3 +36,22 @@ Future getOrgId() async { } return null; } + +Future>?> getUserServices() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + final List? services = userData["service"]; + + if (services != null) { + return services.cast>(); + } + } catch (e) { + return null; + } + } + return null; +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..64a0ece 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..2db3c22 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 438e7e5..c0be2ee 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,9 +6,11 @@ import FlutterMacOS import Foundation import file_picker +import file_selector_macos import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 4587617..1248b87 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -129,6 +129,38 @@ packages: url: "https://pub.dev" source: hosted version: "10.0.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" flutter: dependency: "direct main" description: flutter @@ -184,6 +216,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9" + url: "https://pub.dev" + source: hosted + version: "0.8.12+22" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" intl: dependency: "direct main" description: @@ -264,6 +360,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" nested: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 08f2523..50f6d6e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,7 @@ dependencies: file_picker: ^10.0.0 bcrypt: ^1.1.3 http_parser: ^4.1.2 + image_picker: ^1.1.2 dev_dependencies: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..77ab7a0 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..a423a02 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST