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:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; import 'package:image_picker/image_picker.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../config/apiUrl.dart'; import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../../widgets/custom_breadcrumb_navigation.dart'; class OrgSetUp extends StatefulWidget { @override _OrgSetUpState createState() => _OrgSetUpState(); } class _OrgSetUpState extends State { final ApiService apiService = ApiService(); String? userId; String? orgId; String? token; bool isViewMode = false; bool showMail = false; Map errorMessages = {}; final Map controllers = {}; List? apiAllServices; Map? selectedOrg; Color? layoutColor; Color? bodyColor; bool isSelected = false; Uint8List? _webImage; Uint8List? _imageBytes; final TextEditingController _orgNameController = TextEditingController(); // List selectedServiceIds = []; List> selectedServiceIds = []; @override void initState() { super.initState(); _checkAuthAndLoadData(); // WidgetsBinding.instance.addPostFrameCallback((_) { // loadAllServices(); // getOrganizationData(); // initializeData(); // loadInitialData(); // }); } void _checkAuthAndLoadData() async { final String? token = await getToken(); // Your async function to get token if (token == null || token.isEmpty) { // Token doesn't exist → redirect to login context.go( "/", ); // or use: router.go("/") if you're using `GoRouter` directly return; } loadAllServices(); getOrganizationData(); initializeData(); loadInitialData(); } void loadInitialData() async { String? layoutString = await getLayoutColor(); String? bodyStringColor = await getBodyColor(); setState(() { layoutColor = layoutString != null ? Color(int.parse(layoutString)) : Colors.redAccent; bodyColor = bodyStringColor != null ? Color(int.parse(bodyStringColor)) : Colors.white; }); } Map mailConfig = {}; Map get orgData { final data = { "org_id": 1, "name": _orgNameController.text, "logo": null, "sender_email": mailConfig['sender_email'], "mail_user_name": mailConfig['mail_user_name'], "mail_password": mailConfig['mail_password'], "mail_host": mailConfig['mail_host'], "mail_port": mailConfig['mail_port'], "layout_color": "0x${layoutColor?.toARGB32().toRadixString(16).padLeft(8, '0').toUpperCase()}", "color": "0x${bodyColor?.toARGB32().toRadixString(16).padLeft(8, '0').toUpperCase()}", "services_ids": jsonEncode(selectedServiceIds), "created_by": null, "updated_by": null, "is_active": 1, // "org_id": orgId, // "created_by": userId, // "updated_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; } 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 initializeData() async { token = await getToken(); userId = await getUserId(); if (token == null || userId == null) { print("Token or USerId missing"); print("Retrieved Token: $token"); print("Retrieved UserId: $userId"); return; } else { setState(() {}); } } Future getOrganizationData() async { try { print("getUpdatedServices"); final prefs = await SharedPreferences.getInstance(); final String? orgDataString = prefs.getString('org_data'); if (orgDataString != null) { final Map orgData = jsonDecode(orgDataString); print("UUPdatedServices - $orgData"); setState(() { selectedOrg = orgData; String? rawLogoPath = selectedOrg?['logo']; if (rawLogoPath != null && rawLogoPath.contains('/assets')) { const baseUrl = "https://apitest.tripapprovaltool.com"; final assetPath = rawLogoPath.split('/assets').last; selectedOrg!['logo'] = "$baseUrl/assets$assetPath"; } _orgNameController.text = selectedOrg?['name']; layoutColor = selectedOrg?['layout_color'] != null ? Color( int.parse( selectedOrg!['layout_color'].toString().replaceFirst( '0x', '', ), radix: 16, ), ) : Colors.white; bodyColor = selectedOrg?['color'] != null ? Color( int.parse( selectedOrg!['color'].toString().replaceFirst('0x', ''), radix: 16, ), ) : Colors.blue; // Set mail config fields mailConfig['sender_email'] = selectedOrg?['sender_email']; mailConfig['mail_user_name'] = selectedOrg?['mail_user_name']; mailConfig['mail_password'] = selectedOrg?['mail_password']; mailConfig['mail_host'] = selectedOrg?['mail_host']; mailConfig['mail_port'] = selectedOrg?['mail_port']; // Set selected service IDs // final services = selectedOrg?['services_ids'] as List? ?? []; // selectedServiceIds = // services.map((item) => item['service_id'].toString()).toList(); // final servicesRaw = selectedOrg?['services_ids']; List services; if (servicesRaw is String) { try { services = jsonDecode(servicesRaw); } catch (e) { print('❌ Failed to decode services_ids: $e'); services = []; } } else if (servicesRaw is List) { services = servicesRaw; } else { services = []; } selectedServiceIds = services.map>((item) { // force cast or copy to a regular map final map = Map.from(item); return {"service_id": map['service_id'].toString()}; }).toList(); }); orgId = await getOrgId(); print("selectedOrg - $selectedOrg"); print("mailConfig - $mailConfig"); } // final result = await apiService.fetchOrganization(); } catch (e) { print('Error fetching updatedServices 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); }); } } Future createOrgData(Map userData) async { final bool isUpdating = selectedOrg != null && selectedOrg!.isNotEmpty; final uri = Uri.parse( isUpdating ? '$apiUrl/api/organizations/update/${selectedOrg?["org_id"]}' : '$apiUrl/api/organizations/create', ); if (token == null) { throw Exception('Token not found. Please log in.'); } // Use MultipartRequest (POST only) final request = http.MultipartRequest('POST', uri); request.headers['Authorization'] = 'Bearer $token'; // If updating, spoof the method Laravel-style if (isUpdating) { request.fields['_method'] = 'PUT'; request.fields['org_id'] = selectedOrg!["org_id"].toString(); print("UpdatingLarvel..."); } // Add all non-null and non-empty user data fields userData.forEach((key, value) { if (value != null && value.toString().trim().isNotEmpty) { request.fields[key] = value.toString(); } }); if (_imageBytes != null) { final multipartFile = http.MultipartFile.fromBytes( 'logo', // 👈 this should match the key expected by your backend _imageBytes!, filename: 'logo.png', contentType: MediaType('image', 'png'), ); request.files.add(multipartFile); print("📎 Logo image attached."); } else { print("⚠️ No logo selected."); } print("🚀 Sending request with fields: ${request.fields}"); try { final streamedResponse = await request.send(); final response = await http.Response.fromStream(streamedResponse); print("Response status: ${response.statusCode}"); print("Response body: ${response.body}"); if (response.statusCode == 200 || response.statusCode == 201) { print("✅ User submitted successfully!"); print("📨 Response Organizt Update: ${response.body}"); final data = json.decode(response.body); if (!data.containsKey('data') || data['data'] is! Map) { throw Exception( "Invalid response format: 'data' field is missing or not a Map", ); } // Make sure each item is a Map final Map orgList = Map.from( data['data'], ); print(orgList); await updateOrgDataWithNewValues(orgList); print("📨 Response Organizt Update:"); // return orgList; context.go('/OrganizationSettings'); // context.go('/listPlan'); } else { print("❌ Submission failed. Status: ${response.statusCode}"); print("📨 Body: ${response.body}"); } } catch (e) { print("🔥 Error submitting user: $e"); } } Future updateOrgDataWithNewValues(Map newData) async { final prefs = await SharedPreferences.getInstance(); final String? orgDataString = prefs.getString('org_data'); Map orgData = {}; if (orgDataString != null) { try { orgData = jsonDecode(orgDataString); layoutColor = orgData['layout_color'] != null ? Color( int.parse( orgData['layout_color'].toString().replaceFirst('0x', ''), radix: 16, ), ) : Colors.white; } catch (e) { print('❌ Failed to decode org_data: $e'); } } // Merge in the new data orgData.addAll(newData); // Save back await prefs.setString('org_data', jsonEncode(orgData)); await prefs.setString('layout_color', orgData['layout_color']); print("✅ Updated org_data saved."); } void handleSubmit() { print("HandleSubmiy - $orgData"); createOrgData(orgData); setState(() { if (!isValidData(orgData)) { print("USERDETAILS : $orgData"); print("Validation Failed: Required fields are missing."); return; // Stop execution if validation fails } else { print("USERDETAILS : $orgData"); createOrgData(orgData); } }); } @override Widget build(BuildContext context) { return ResponsiveBuilder( builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( // backgroundColor: Colors.white, backgroundColor: Color(0xFFf5f5f5), appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop ? EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.1, // 30% of screen width as horizontal padding vertical: MediaQuery.of(context).size.height * 0, // 5% of screen height as vertical padding ) : EdgeInsets.all(0), child: Row( children: [ // if (isDesktop) CustomDrawer(isDesktop: true), Expanded(child: buildOrganizationLayout(isDesktop)), ], ), ), ); }, ); } Widget buildOrganizationLayout(isDesktop) { return Container( margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null, padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: isDesktop ? Colors.white : Color(0xFFFCFCFC), ), // decoration: BoxDecoration( // // color: Colors.amber, // color: bodyColor, // border: Border.all( // color: Colors.white, // // color: Color(0xFFF7F7FB), // width: 3.5)), child: Column( // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Container( // color: Colors.redAccent, // color: bodyColor, child: buildOrgLayout(isDesktop), ), ), Container( padding: const EdgeInsets.all(5), color: Colors.white, child: isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.end, // children: [Text("Button")], children: _buildSubmit( isDesktop, isViewMode, layoutColor, ), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: _buildSubmit( isDesktop, isViewMode, layoutColor, ), ), ), ], ), ); } Widget buildOrgLayout(bool isDesktop) { final screenWidth = MediaQuery.of(context).size.width; final screenHeight = MediaQuery.of(context).size.height; final double responsiveLogoWidth = screenWidth * 0.15; // 15% of screen width final double responsiveLogoHeight = screenHeight * 0.07; // 7% of screen height final double largeResponsiveLogoWidth = screenWidth * 0.6; // 60% of screen width final double largeResponsiveLogoHeight = screenHeight * 0.15; Future _pickImage() async { final picker = ImagePicker(); final XFile? pickedFile = await picker.pickImage( source: ImageSource.gallery, ); if (pickedFile != null && kIsWeb) { try { final bytes = await pickedFile.readAsBytes(); print('✅ Image loaded, size: ${bytes.length} bytes'); setState(() { _imageBytes = bytes; }); } catch (e) { print('❌ Error reading image bytes: $e'); } } else { print('⚠️ Image picking canceled or not on web.'); } } return Container( // margin: isDesktop // ? EdgeInsets.all(10.0) // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), height: isDesktop ? MediaQuery.of(context).size.height * 0.98 : MediaQuery.of(context).size.height, // decoration: BoxDecoration( // border: isDesktop // ? Border.all( // width: 2, // color: Colors.white, // // color: Color(0xFFF7F7FB), // ) // : null, // color: Colors.white, // // color: Color(0xFFF7F7FB), // // // color: Colors.amber, // ), child: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( children: [ Container( padding: const EdgeInsets.only( left: 20, right: 20, bottom: 20, top: 5, ), // height: MediaQuery.of(context).size.height * 0.8, color: Colors.white, child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( color: Colors.white, child: Row( mainAxisAlignment: MainAxisAlignment.start, // mainAxisAlignment: MainAxisAlignment.center, children: [ Container( child: BreadcrumbNavigation( isDesktop: isDesktop, breadcrumbItems: [ BreadcrumbItem( title: 'Organization Settings', tooltip: 'Go To Organization Settings', onTap: (context) { context.go("/OrganizationSettings"); }, ), BreadcrumbItem( title: selectedOrg != null && selectedOrg!.isNotEmpty ? "Update Organization" : "Create Organization", ), ], ), ), // Text( // selectedOrg != null && selectedOrg!.isNotEmpty // ? "Update Organization" // : "Create Organization", // style: GoogleFonts.poppins( // fontSize: 15, // fontWeight: FontWeight.w500, // ), // ), ], ), ), Container( color: Colors.white, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment .center, // now -> .center , old -> .start children: [ Padding( padding: const EdgeInsets.only(top: 1.0), child: Text( "Name:", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF212121), ), ), ), SizedBox(width: 8), Expanded( child: TextFormField( controller: _orgNameController, style: GoogleFonts.poppins( fontSize: 16, color: Color(0xFF114D8B), ), decoration: InputDecoration( hintText: "Enter Organization Name", hintStyle: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, isDense: true, // contentPadding: // EdgeInsets.symmetric(vertical: 14), ), // textAlignVertical: TextAlignVertical.center, ), ), Spacer(), GestureDetector( onTap: _pickImage, child: _imageBytes != null ? ClipOval( child: Image.memory( _imageBytes!, // width: 50, // height: 50, width: responsiveLogoWidth, // Use responsive width height: responsiveLogoHeight, fit: BoxFit.contain, ), ) : selectedOrg?['logo'] != null ? ClipRect( child: Image.network( selectedOrg!['logo'], width: responsiveLogoWidth, // Use responsive width height: responsiveLogoHeight, // width: 250, // height: 55, fit: BoxFit.contain, errorBuilder: ( context, error, stackTrace, ) { return const CircleAvatar( radius: 20, backgroundColor: Colors.redAccent, child: Icon(Icons.error, size: 10), ); }, ), ) : const CircleAvatar( radius: 20, backgroundColor: Colors.amber, child: Icon(Icons.add_a_photo, size: 10), ), ), ], ), ), SizedBox(height: 10), Text( "Services", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF212121), ), ), SizedBox(height: 10), Container( decoration: BoxDecoration( border: Border.all(color: Color(0xFFF4F4FB)), borderRadius: BorderRadius.circular(1), // color: bodyColor, // color: Color(0xFFF5F5F5), color: Colors.white, ), padding: EdgeInsets.only( left: 5, right: 5, top: 15, bottom: 5, ), child: isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, // mainAxisSize: MainAxisSize.min, children: _buildOptions(), ) : Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row(children: _buildOptions()), ), ), ), SizedBox(height: 15), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Choose Theme", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF212121), ), ), 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: 5, ), child: layoutColor != null && bodyColor != null ? ColorThemePickerWidget( initialLayoutColor: layoutColor, initialBodyColor: bodyColor, onLayoutColorSelected: ( Color selectedLayoutColor, ) { setState(() { layoutColor = selectedLayoutColor; }); }, onBodyColorSelected: ( Color selectedBodyColor, ) { setState(() { bodyColor = selectedBodyColor; }); }, ) : CircularProgressIndicator(), ), ], ), SizedBox(height: 15), Container( color: Colors.white, child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Mail Settings", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF212121), ), ), // GestureDetector( // onTap: () { // setState(() { // showMail = !showMail; // }); // }, // child: Icon( // Icons.keyboard_arrow_down_outlined, // color: Color(0xFF114D8B), // size: 30, // ), // ), ], ), // if (showMail) SizedBox(height: 10), Container( // width: double.infinity, decoration: BoxDecoration( border: Border.all( color: Color(0xFFF5F5F5), // color: bodyColor ?? Colors.grey, width: 1.5, ), // color: bodyColor, color: Colors.white70, // color: Color(0xFFF5F5F5), ), child: Row( mainAxisAlignment: isDesktop ? MainAxisAlignment.start : MainAxisAlignment.center, children: [ mailConfig['sender_email'] != null ? MailSetting( isDesktop: isDesktop, initialMailData: mailConfig, onMailDataChanged: (updatedData) { // You can setState here or do something else with updatedData print("Updated Mail Data: $updatedData"); mailConfig = updatedData; }, ) : CircularProgressIndicator(), ], ), ), ], ), ), // 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); // final idMap = {"service_id": service['service_id'].toString()}; // final isSelected = selectedServiceIds.contains(idMap); String serviceId = service['service_id'].toString(); // bool isSelected = selectedServiceIds.contains(serviceId); bool isSelected = selectedServiceIds.any( (item) => item["service_id"] == serviceId, ); return GestureDetector( onTap: () { setState(() { String serviceId = service['service_id'].toString(); // Check if already selected int existingIndex = selectedServiceIds.indexWhere( (item) => item["service_id"] == serviceId, ); if (existingIndex != -1) { selectedServiceIds.removeAt(existingIndex); } else { selectedServiceIds.add({"service_id": serviceId}); } }); }, child: Row( children: [ iconUrl.isNotEmpty ? Image.network( iconUrl, width: 18, height: 18, errorBuilder: (context, error, stackTrace) { return Icon( fallbackIcon, size: 18, color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), ); }, ) : Icon( fallbackIcon, size: 18, color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), ), SizedBox(width: 2), Text( name, style: GoogleFonts.poppins( fontSize: 12, color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), fontWeight: isSelected == name ? FontWeight.bold : FontWeight.w500, ), // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), ), SizedBox(width: 2), // if (selectedListOption == title && widget.isViewMode == false) 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_takeoff_outlined; case 'train': return Icons.train_outlined; case 'bus': return Icons.directions_bus_rounded; case 'taxi': return Icons.local_taxi_outlined; case 'accomodation': return Icons.local_hotel_outlined; case 'forex': return Icons.attach_money_outlined; case 'insurance': return Icons.list_alt_outlined; case 'visa': return Icons.badge_outlined; case 'miscellaneous': return Icons.card_giftcard_outlined; default: return Icons.circle_notifications; } } List _buildSubmit(isDesktop, bool isViewMode, Color? layoutColor) { return [ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: layoutColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: layoutColor ?? Colors.grey, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { context.go('/listPlan'); }, child: Text("Cancel", style: GoogleFonts.poppins(fontSize: 12)), ), SizedBox(width: 20), MouseRegion( // cursor: widget.isViewMode // ? SystemMouseCursors.forbidden // : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: layoutColor, // Keep original color foregroundColor: Colors.white, // Keep original color disabledBackgroundColor: layoutColor, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: layoutColor ?? Colors.grey, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: handleSubmit, // Disable when in view mode child: Text("Submit", style: GoogleFonts.poppins(fontSize: 12)), ), ), ]; } }