1056 lines
36 KiB
Dart
1056 lines
36 KiB
Dart
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<OrgSetUp> {
|
|
final ApiService apiService = ApiService();
|
|
String? userId;
|
|
String? orgId;
|
|
String? token;
|
|
|
|
bool isViewMode = false;
|
|
bool showMail = false;
|
|
|
|
Map<String, String> errorMessages = {};
|
|
final Map<String, TextEditingController> controllers = {};
|
|
|
|
List<dynamic>? apiAllServices;
|
|
Map<String, dynamic>? selectedOrg;
|
|
Color? layoutColor;
|
|
Color? bodyColor;
|
|
|
|
bool isSelected = false;
|
|
|
|
Uint8List? _webImage;
|
|
Uint8List? _imageBytes;
|
|
final TextEditingController _orgNameController = TextEditingController();
|
|
|
|
// List<String> selectedServiceIds = [];
|
|
|
|
List<Map<String, dynamic>> 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<String, dynamic> mailConfig = {};
|
|
|
|
Map<String, dynamic> 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<void> loadAllServices() async {
|
|
try {
|
|
final result = await apiService.fetchAllServices();
|
|
setState(() {
|
|
apiAllServices = result;
|
|
});
|
|
print("Fetched services: $apiAllServices");
|
|
} catch (e) {
|
|
print('Error fetching role list: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> 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<void> getOrganizationData() async {
|
|
try {
|
|
print("getUpdatedServices");
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final String? orgDataString = prefs.getString('org_data');
|
|
|
|
if (orgDataString != null) {
|
|
final Map<String, dynamic> 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<dynamic>? ?? [];
|
|
// selectedServiceIds =
|
|
// services.map((item) => item['service_id'].toString()).toList();
|
|
//
|
|
|
|
final servicesRaw = selectedOrg?['services_ids'];
|
|
|
|
List<dynamic> 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<Map<String, dynamic>>((item) {
|
|
// force cast or copy to a regular map
|
|
final map = Map<String, dynamic>.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<String, dynamic> data) {
|
|
errorMessages.clear(); // Reset errors
|
|
|
|
// Required fields that must not be empty
|
|
List<String> 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<void> createOrgData(Map<String, dynamic> 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<String, dynamic>
|
|
final Map<String, dynamic> orgList = Map<String, dynamic>.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<void> updateOrgDataWithNewValues(Map<String, dynamic> newData) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final String? orgDataString = prefs.getString('org_data');
|
|
|
|
Map<String, dynamic> 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<void> _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<Widget> _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<String, dynamic> 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<Widget> _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)),
|
|
),
|
|
),
|
|
];
|
|
}
|
|
}
|