Page Shaking
This commit is contained in:
parent
b88973d43c
commit
3e3ef56694
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Future<dynamic> showApprovalDialog(
|
||||
BuildContext context, Color layoutColor) async {
|
||||
BuildContext context,
|
||||
Color layoutColor,
|
||||
) async {
|
||||
String selectedAction = ""; // "", "accept", "reject"
|
||||
String remarks = "";
|
||||
|
||||
@ -24,13 +26,12 @@ Future<dynamic> showApprovalDialog(
|
||||
Text(
|
||||
"To Approve or Reject Trip",
|
||||
style: TextStyle(
|
||||
fontFamily: "Inter", fontWeight: FontWeight.w500),
|
||||
fontFamily: "Inter",
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
size: 15,
|
||||
),
|
||||
icon: const Icon(Icons.close, size: 15),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, null); // Close the dialog
|
||||
},
|
||||
@ -45,12 +46,14 @@ Future<dynamic> showApprovalDialog(
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: selectedAction == "accept"
|
||||
? layoutColor
|
||||
: Colors.grey.shade200,
|
||||
foregroundColor: selectedAction == "accept"
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
backgroundColor:
|
||||
selectedAction == "accept"
|
||||
? Colors.green
|
||||
: Colors.grey.shade200,
|
||||
foregroundColor:
|
||||
selectedAction == "accept"
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
@ -68,12 +71,14 @@ Future<dynamic> showApprovalDialog(
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: selectedAction == "reject"
|
||||
? Colors.redAccent
|
||||
: Colors.grey.shade200,
|
||||
foregroundColor: selectedAction == "reject"
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
backgroundColor:
|
||||
selectedAction == "reject"
|
||||
? Colors.redAccent
|
||||
: Colors.grey.shade200,
|
||||
foregroundColor:
|
||||
selectedAction == "reject"
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
@ -122,12 +127,16 @@ Future<dynamic> showApprovalDialog(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(
|
||||
color: Colors.blueGrey, width: 0.5),
|
||||
color: Colors.blueGrey,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide:
|
||||
BorderSide(color: Colors.blueGrey, width: 0.5),
|
||||
borderSide: BorderSide(
|
||||
color: Colors.blueGrey,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
@ -135,7 +144,7 @@ Future<dynamic> showApprovalDialog(
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
],
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
@ -187,94 +196,14 @@ Future<dynamic> showApprovalDialog(
|
||||
Future<bool?> showApproveDialog1(BuildContext context, Color layoutColor) {
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
title: const Text(
|
||||
"Confirm Approval",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
content: const Text("Are you sure you want to approve this plan?"),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: layoutColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: layoutColor, width: 2),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: layoutColor,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: layoutColor, width: 2),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text("OK"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Show confirm dialog for rejection with remarks input
|
||||
Future<String?> showRejectDialog1(
|
||||
BuildContext context, Color layoutColor) async {
|
||||
String remarks = "";
|
||||
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) => AlertDialog(
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
contentPadding: const EdgeInsets.all(36),
|
||||
// title: const Text("Confirm Rejection"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
"Confirm Rejection",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text("Please enter reason for rejection."),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
maxLines: 3,
|
||||
onChanged: (value) => remarks = value,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "Remarks...",
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 10, // 👈 Set your desired font size here
|
||||
color: Colors.grey,
|
||||
fontFamily: "Inter", // optional if you want consistent font
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.blueGrey, width: 0.5),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.blueGrey, width: 0.5),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
title: const Text(
|
||||
"Confirm Approval",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
content: const Text("Are you sure you want to approve this plan?"),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
@ -299,14 +228,102 @@ Future<String?> showRejectDialog1(
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
onPressed: () {
|
||||
if (remarks.trim().isEmpty) return;
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text("OK"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Show confirm dialog for rejection with remarks input
|
||||
Future<String?> showRejectDialog1(
|
||||
BuildContext context,
|
||||
Color layoutColor,
|
||||
) async {
|
||||
String remarks = "";
|
||||
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder:
|
||||
(context, setState) => AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
contentPadding: const EdgeInsets.all(36),
|
||||
// title: const Text("Confirm Rejection"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
"Confirm Rejection",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text("Please enter reason for rejection."),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
maxLines: 3,
|
||||
onChanged: (value) => remarks = value,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "Remarks...",
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 10, // 👈 Set your desired font size here
|
||||
color: Colors.grey,
|
||||
fontFamily:
|
||||
"Inter", // optional if you want consistent font
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.blueGrey,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.blueGrey,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: layoutColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: layoutColor, width: 2),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: layoutColor,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: layoutColor, width: 2),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
onPressed: () {
|
||||
if (remarks.trim().isEmpty) return;
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
child: const Text("OK"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -42,7 +42,7 @@ class _PlaceholdersModalState extends State<PlaceholdersModal> {
|
||||
content: Container(
|
||||
width:
|
||||
isDesktop
|
||||
? MediaQuery.of(context).size.width * 0.3
|
||||
? MediaQuery.of(context).size.width * 0.4
|
||||
: double.maxFinite,
|
||||
// Set max height so ListView knows constraints
|
||||
height: 300,
|
||||
|
||||
@ -642,7 +642,7 @@ class TemplateState extends State<Template> {
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
height: 200,
|
||||
height: MediaQuery.of(context).size.height * 0.3,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: Color(0xFFD6D5E6), width: 0.5),
|
||||
|
||||
830
lib/Screens/myTemplates/templateForex.dart
Normal file
830
lib/Screens/myTemplates/templateForex.dart
Normal file
@ -0,0 +1,830 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io' as io show Directory, File;
|
||||
import 'package:delta_to_html/delta_to_html.dart';
|
||||
import 'package:flutter/cupertino.dart' as dom;
|
||||
import 'package:flutter_quill/flutter_quill.dart' hide Text;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart' hide Text;
|
||||
import 'package:flutter_quill/quill_delta.dart';
|
||||
import 'package:flutter_quill/quill_delta.dart' as quill;
|
||||
import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart';
|
||||
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
|
||||
import 'package:frontend/Screens/myTemplates/templateForex.dart'
|
||||
as _editorFocusNode;
|
||||
import 'package:frontend/Screens/myTemplates/templateForex.dart'
|
||||
as _editorScrollController;
|
||||
import 'package:frontend/Screens/myTemplates/templateForex.dart' as _controller;
|
||||
import 'package:html2md/html2md.dart' as html2md;
|
||||
import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart';
|
||||
|
||||
import 'package:flutter_quill/flutter_quill.dart' as quill;
|
||||
import 'package:html/parser.dart' show parse;
|
||||
import 'package:html/dom.dart' as dom hide Element, Text;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_quill/flutter_quill_internal.dart';
|
||||
import 'package:flutter_quill/quill_delta.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
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 '../../utils/auth_utils.dart';
|
||||
import '../../widgets/custom_user_travel.dart';
|
||||
import 'dialog_placeholders.dart';
|
||||
|
||||
class TemplateForex extends StatefulWidget {
|
||||
final Map<String, dynamic>? templateData;
|
||||
|
||||
const TemplateForex({super.key, required this.templateData});
|
||||
|
||||
static TemplateForex fromState(GoRouterState state) {
|
||||
return TemplateForex(templateData: state.extra as Map<String, dynamic>?);
|
||||
}
|
||||
|
||||
@override
|
||||
TemplateForexState createState() => TemplateForexState();
|
||||
}
|
||||
|
||||
class TemplateForexState extends State<TemplateForex> {
|
||||
final ApiService apiService = ApiService();
|
||||
|
||||
// final QuillController _controller = QuillController.basic();
|
||||
String? orgId;
|
||||
String? userId;
|
||||
Color layoutColor = Colors.redAccent;
|
||||
Color bodyColor = Colors.white;
|
||||
final Map<String, TextEditingController> controllers = {};
|
||||
List<String> dataHeader = ["subject"];
|
||||
List<String> placeholders = [];
|
||||
|
||||
late QuillController _controller = QuillController.basic();
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
|
||||
late int templateId = 0;
|
||||
late String templateName = "";
|
||||
late List<Map<String, dynamic>> placeholderList = [];
|
||||
|
||||
Map<String, dynamic> get TemplateData {
|
||||
final data = {
|
||||
"org_id": orgId,
|
||||
|
||||
// "template_id": templateId,
|
||||
// "template_name": controllers["templateName"]?.text,
|
||||
"template_id": templateId,
|
||||
"template_name": templateName,
|
||||
"subject": controllers["subject"]?.text,
|
||||
"body_html": DeltaToHTML.encodeJson(
|
||||
_controller.document.toDelta().toJson(),
|
||||
),
|
||||
// "body_html": jsonEncode(_controller.document.toDelta().toJson()),
|
||||
|
||||
// "body_html": _controller,
|
||||
// "body_html": convertQuillDocToHtml(_controller.document),
|
||||
// ✅ convert delta to HTML
|
||||
"placeholder": jsonEncode(placeholderList),
|
||||
// "created_by": userId
|
||||
};
|
||||
print('start 123');
|
||||
print(jsonEncode(_controller.document.toDelta().toJson()));
|
||||
// print(jsonEncode(_controller.document));
|
||||
// Only add group_id if it's an edit operation
|
||||
// if (widget.templateData != null && widget.templateData!.containsKey('group_id')) {
|
||||
// data["template_id"] = templateData;
|
||||
// }
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
for (var field in dataHeader) {
|
||||
controllers[field] = TextEditingController();
|
||||
}
|
||||
|
||||
updateData();
|
||||
loadinitializeData();
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
@override
|
||||
// void dispose() {
|
||||
// // controllers.dispose();
|
||||
// // _editorScrollController.dispose();
|
||||
// _editorFocusNode.dispose();
|
||||
// super.dispose();
|
||||
// }
|
||||
void loadinitializeData() async {
|
||||
orgId = await getOrgId();
|
||||
userId = await getUserId();
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
String convertQuillDocToHtml(quill.Document doc) {
|
||||
final buffer = StringBuffer();
|
||||
|
||||
print("convertQuillDocToHtml");
|
||||
for (final op in doc.toDelta().toList()) {
|
||||
final insert = op.data;
|
||||
final attrs = op.attributes ?? {};
|
||||
|
||||
if (insert is String) {
|
||||
var content = insert;
|
||||
|
||||
// Handle formatting (bold, italic, etc.)
|
||||
if (attrs.containsKey('bold')) {
|
||||
content = '<strong>$content</strong>';
|
||||
}
|
||||
if (attrs.containsKey('italic')) {
|
||||
content = '<em>$content</em>';
|
||||
}
|
||||
|
||||
// Wrap each paragraph with <p>
|
||||
if (content.trim().isNotEmpty) {
|
||||
buffer.write('<p>${content.trim()}</p>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
String convertQuillDocToHtml2(quill.Document doc) {
|
||||
final buffer = StringBuffer();
|
||||
final lines = <String>[];
|
||||
final delta = doc.toDelta();
|
||||
|
||||
String applyStyles(String text, Map<String, dynamic>? attrs) {
|
||||
if (attrs == null) return text;
|
||||
if (attrs.containsKey('bold')) {
|
||||
text = '<strong>$text</strong>';
|
||||
}
|
||||
if (attrs.containsKey('italic')) {
|
||||
text = '<em>$text</em>';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
for (final op in delta.toList()) {
|
||||
final insert = op.data;
|
||||
final attrs = op.attributes;
|
||||
|
||||
if (insert is String) {
|
||||
final parts = insert.split('\n');
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
final part = applyStyles(parts[i], attrs);
|
||||
lines.add(part);
|
||||
|
||||
if (i < parts.length - 1) {
|
||||
// End of line: wrap accumulated content into <p>
|
||||
final joined = lines.join('');
|
||||
if (joined.trim().isNotEmpty) {
|
||||
buffer.writeln('<p>${joined.trim()}</p>');
|
||||
}
|
||||
lines.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add remaining lines
|
||||
final joined = lines.join('');
|
||||
if (joined.trim().isNotEmpty) {
|
||||
buffer.writeln('<p>${joined.trim()}</p>');
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
String extractPlainTextFromHtml(String html) {
|
||||
final regex = RegExp(r'<p>(.*?)<\/p>', multiLine: true, dotAll: true);
|
||||
final matches = regex.allMatches(html);
|
||||
|
||||
final buffer = StringBuffer();
|
||||
for (final match in matches) {
|
||||
final text = match.group(1)?.replaceAll(RegExp(r'<[^>]*>'), '') ?? '';
|
||||
buffer.writeln(text.trim());
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
String decodeHtmlEntities(String text) {
|
||||
return text
|
||||
.replaceAll(' ', ' ')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll(''', "'"); // add more as needed
|
||||
}
|
||||
|
||||
// quill.Document convertSimpleHtmlToQuill(String htmlString) {
|
||||
// final delta = quill.Delta();
|
||||
// final doc = html_parser.parse(htmlString);
|
||||
// final body = doc.body;
|
||||
//
|
||||
// void walk(Node node) {
|
||||
// if (node is Text) {
|
||||
// delta.insert(node.text);
|
||||
// } else if (node is Element) {
|
||||
// switch (node.localName) {
|
||||
// case 'p':
|
||||
// node.nodes.forEach(walk);
|
||||
// delta.insert('\n');
|
||||
// break;
|
||||
// case 'br':
|
||||
// delta.insert('\n');
|
||||
// break;
|
||||
// case 'strong':
|
||||
// case 'b':
|
||||
// delta.insert(node.text, {'bold': true});
|
||||
// break;
|
||||
// case 'em':
|
||||
// case 'i':
|
||||
// delta.insert(node.text, {'italic': true});
|
||||
// break;
|
||||
// case 'a':
|
||||
// delta.insert(node.text, {'link': node.attributes['href']});
|
||||
// break;
|
||||
// default:
|
||||
// node.nodes.forEach(walk);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (body != null) {
|
||||
// walk(body);
|
||||
// }
|
||||
//
|
||||
// return quill.Document.fromDelta(delta..insert('\n'));
|
||||
// }
|
||||
|
||||
String formatTemplateName(String input) {
|
||||
return input
|
||||
.split('_') // split by underscore
|
||||
.map(
|
||||
(word) =>
|
||||
word.isNotEmpty
|
||||
? '${word[0].toUpperCase()}${word.substring(1)}'
|
||||
: '',
|
||||
)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
Future<void> updateData() async {
|
||||
// Ensure apiselectedUser is not null before printing
|
||||
if (widget.templateData != null) {
|
||||
print("API Selected User Has Data - ${widget.templateData}");
|
||||
print(
|
||||
"API Selected User Has - ${widget.templateData?["templateData"]?["subject"]}",
|
||||
);
|
||||
setState(() {
|
||||
// ✅ Wrap in setState to update the UI
|
||||
controllers["templateName"]?.text =
|
||||
widget.templateData?["templateData"]?["template_name"] ?? "";
|
||||
|
||||
controllers["subject"]?.text =
|
||||
widget.templateData?["templateData"]?["subject"] ?? "";
|
||||
final bodyHtml =
|
||||
widget.templateData?["templateData"]?["body_html"] ?? "";
|
||||
|
||||
print("bodyHtml - $bodyHtml");
|
||||
|
||||
String html = widget.templateData?["templateData"]?["body_html"] ?? "";
|
||||
|
||||
final htmlToDelta = HtmlToDelta();
|
||||
|
||||
// Convert the HTML string to Quill Delta format
|
||||
// This is where the magic happens, but also where complex HTML might be simplified
|
||||
final quill.Delta initialDelta = htmlToDelta.convert(html);
|
||||
|
||||
// Create a Quill Document from the Delta
|
||||
final quill.Document quillDoc = quill.Document.fromDelta(initialDelta);
|
||||
|
||||
// Initialize the QuillController with the converted document
|
||||
_controller = quill.QuillController(
|
||||
document: quillDoc,
|
||||
selection: const TextSelection.collapsed(
|
||||
offset: 0,
|
||||
), // Cursor at the start
|
||||
);
|
||||
|
||||
// final plainText = extractPlainTextFromHtml(bodyHtml);
|
||||
// final decodedText = decodeHtmlEntities(plainText);
|
||||
// final quillDoc = quill.Document()..insert(0, decodedText);
|
||||
// _controller = quill.QuillController(
|
||||
// document: quillDoc,
|
||||
// selection: const TextSelection.collapsed(offset: 0),
|
||||
// );
|
||||
|
||||
templateName =
|
||||
widget.templateData?["templateData"]?["template_name"] ?? "";
|
||||
|
||||
print("Fetched template_name: $templateName");
|
||||
|
||||
final rawPlaceholder =
|
||||
widget.templateData?["templateData"]?["placeholder"];
|
||||
|
||||
if (rawPlaceholder is String) {
|
||||
// If it's a JSON string, decode it first
|
||||
placeholderList = List<Map<String, dynamic>>.from(
|
||||
jsonDecode(rawPlaceholder),
|
||||
);
|
||||
} else if (rawPlaceholder is List) {
|
||||
// If it's already a list (ideal case)
|
||||
placeholderList = List<Map<String, dynamic>>.from(rawPlaceholder);
|
||||
}
|
||||
|
||||
print("Extracted placeholders: $placeholders");
|
||||
|
||||
print("Fetched placeholders: $placeholderList");
|
||||
|
||||
templateId =
|
||||
int.tryParse(
|
||||
widget.templateData?["templateData"]?["template_id"]
|
||||
?.toString() ??
|
||||
'0',
|
||||
) ??
|
||||
0;
|
||||
print("Fetched template_id: $templateId");
|
||||
|
||||
// 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<void> handleSubmit() async {
|
||||
Map<String, dynamic> data = TemplateData;
|
||||
|
||||
print('TemplateData - $data');
|
||||
// final String deltaJsonString = TemplateData?["body_html"] ?? "[]";
|
||||
//
|
||||
// print('deltaJsonString $deltaJsonString');
|
||||
//
|
||||
// // Decode the JSON string into a list
|
||||
// final List<dynamic> deltaJson = jsonDecode(deltaJsonString);
|
||||
//
|
||||
// print(DeltaToHTML.encodeJson(deltaJson));
|
||||
//
|
||||
// // Then convert it to a Quill document
|
||||
// final doc = Document.fromJson(List<Map<String, dynamic>>.from(deltaJson));
|
||||
//
|
||||
// // Set it to the controller
|
||||
// _controller = QuillController(
|
||||
// document: doc,
|
||||
// selection: const TextSelection.collapsed(offset: 0),
|
||||
// );
|
||||
// print('doc JSON: ${jsonEncode(doc.toDelta().toJson())}');
|
||||
// print('doc plain text: ${doc.toPlainText()}');
|
||||
// print('doc $doc');
|
||||
|
||||
setState(() {
|
||||
updateTemplateData(data);
|
||||
// This triggers UI rebuild with error messages
|
||||
// if (validateData()) {
|
||||
// postGroupData();
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateTemplateData(policyData) async {
|
||||
final String apiUrldata = '$apiUrl/api/template/update/${templateId}';
|
||||
final token = await getToken(); // Fetch token
|
||||
|
||||
if (token == null) {
|
||||
throw Exception('Token not found. Please log in.');
|
||||
}
|
||||
|
||||
try {
|
||||
final response = await http.put(
|
||||
Uri.parse(apiUrldata),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: jsonEncode(policyData), // Convert map to JSON
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
print("policyData submitted successfully!");
|
||||
print("Response: ${response.body}");
|
||||
|
||||
context.go('/templateList');
|
||||
} else {
|
||||
print("Failed to submit policyData. Status: ${response.statusCode}");
|
||||
print("Error: ${response.body}");
|
||||
}
|
||||
} catch (e) {
|
||||
print(" Error submitting policyData: $e");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveBuilder(
|
||||
builder: (context, sizingInfo) {
|
||||
bool isDesktop =
|
||||
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const 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: buildUserTable(
|
||||
isDesktop,
|
||||
context,
|
||||
bodyColor,
|
||||
layoutColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildUserTable(
|
||||
bool isDesktop,
|
||||
context,
|
||||
Color? bodyColor,
|
||||
Color layoutColor,
|
||||
) {
|
||||
return Container(
|
||||
margin: isDesktop ? const EdgeInsets.only(top: 5.0, bottom: 10.0) : null,
|
||||
padding: const EdgeInsets.all(28),
|
||||
decoration: BoxDecoration(
|
||||
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
formatTemplateName(templateName),
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
|
||||
// SizedBox(height: 10),
|
||||
// buildTempalteSubject(isDesktop),
|
||||
//
|
||||
// SizedBox(height: 10),
|
||||
buildTempalteBody(isDesktop),
|
||||
Spacer(),
|
||||
buildActions(isDesktop),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildTempalteSubject(bool isDesktop) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Subject",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldUserTravellerWrapper(
|
||||
isFocused: false,
|
||||
color: Colors.white,
|
||||
width: isDesktop ? MediaQuery.of(context).size.width * 0.8 : null,
|
||||
isDesktop: isDesktop,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
|
||||
controller: controllers["subject"],
|
||||
onChanged: (value) {
|
||||
// _clearError("local_id_num");
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: "Enter the subject",
|
||||
labelStyle: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildTempalteBody(bool isDesktop) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Text(
|
||||
// "Content",
|
||||
// style: GoogleFonts.poppins(
|
||||
// fontSize: 12,
|
||||
// fontWeight: FontWeight.w600,
|
||||
// color: Color(0xFF575A74),
|
||||
// ),
|
||||
// ),
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
color: Color(0xFFFFFEF0),
|
||||
width: isDesktop ? MediaQuery.of(context).size.width * 0.8 : null,
|
||||
child: IconTheme(
|
||||
data: IconThemeData(size: 18), // Set icon size here
|
||||
child: QuillSimpleToolbar(
|
||||
controller: _controller,
|
||||
config: QuillSimpleToolbarConfig(
|
||||
embedButtons: FlutterQuillEmbeds.toolbarButtons(),
|
||||
showClipboardPaste: true,
|
||||
customButtons: [
|
||||
QuillToolbarCustomButtonOptions(
|
||||
icon: const Icon(Icons.add_alarm_rounded),
|
||||
onPressed: () {
|
||||
_controller.document.insert(
|
||||
_controller.selection.extentOffset,
|
||||
TimeStampEmbed(DateTime.now().toString()),
|
||||
);
|
||||
|
||||
_controller.updateSelection(
|
||||
TextSelection.collapsed(
|
||||
offset: _controller.selection.extentOffset + 1,
|
||||
),
|
||||
ChangeSource.local,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
buttonOptions: QuillSimpleToolbarButtonOptions(
|
||||
base: QuillToolbarBaseButtonOptions(
|
||||
afterButtonPressed: () {
|
||||
final isDesktop = {
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.windows,
|
||||
TargetPlatform.macOS,
|
||||
}.contains(defaultTargetPlatform);
|
||||
// if (isDesktop) {
|
||||
// _editorFocusNode.requestFocus();
|
||||
// }
|
||||
},
|
||||
),
|
||||
linkStyle: QuillToolbarLinkStyleButtonOptions(
|
||||
validateLink: (link) {
|
||||
// Treats all links as valid. When launching the URL,
|
||||
// `https://` is prefixed if the link is incomplete (e.g., `google.com` → `https://google.com`)
|
||||
// however this happens only within the editor.
|
||||
return true;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final selected = await showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(context) =>
|
||||
PlaceholdersModal(placeholders: placeholderList),
|
||||
);
|
||||
|
||||
if (selected != null) {
|
||||
print("User selected placeholder: $selected");
|
||||
// You can now insert into a controller or editor
|
||||
// Ensure the editor is focused
|
||||
FocusScope.of(context).requestFocus(_focusNode);
|
||||
|
||||
final selection = _controller.selection;
|
||||
final position = selection.baseOffset;
|
||||
|
||||
// if (position >= 0) {
|
||||
// final intPosition = position.toInt();
|
||||
//
|
||||
// _controller.document.insert(intPosition, selected);
|
||||
//
|
||||
// _controller.updateSelection(
|
||||
// TextSelection.collapsed(
|
||||
// offset: intPosition + selected.length,
|
||||
// ),
|
||||
// ChangeSource.local,
|
||||
// );
|
||||
// }
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.grey,
|
||||
// padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
child: Text(
|
||||
'Placeholders',
|
||||
style: GoogleFonts.poppins(fontSize: 13, color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: Color(0xFFD6D5E6), width: 0.5),
|
||||
),
|
||||
child: QuillEditor(
|
||||
controller: _controller,
|
||||
scrollController: ScrollController(),
|
||||
|
||||
focusNode: _focusNode,
|
||||
config: QuillEditorConfig(
|
||||
placeholder: 'Start writing your notes...',
|
||||
padding: const EdgeInsets.all(16),
|
||||
embedBuilders: [
|
||||
...FlutterQuillEmbeds.editorBuilders(
|
||||
imageEmbedConfig: QuillEditorImageEmbedConfig(
|
||||
imageProviderBuilder: (context, imageUrl) {
|
||||
// https://pub.dev/packages/flutter_quill_extensions#-image-assets
|
||||
if (imageUrl.startsWith('assets/')) {
|
||||
return AssetImage(imageUrl);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
videoEmbedConfig: QuillEditorVideoEmbedConfig(
|
||||
customVideoBuilder: (videoUrl, readOnly) {
|
||||
// To load YouTube videos https://github.com/singerdmx/flutter-quill/releases/tag/v10.8.0
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
TimeStampEmbedBuilder(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildActions(bool isDesktop) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
context.go('/OrganizationSettings');
|
||||
// You can get text from commentController.text
|
||||
Navigator.of(context).pop(); // Close the modal
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: layoutColor,
|
||||
// backgroundColor: widget.layoutColor,
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: layoutColor, width: 2),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: layoutColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
SizedBox(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
handleSubmit();
|
||||
// You can get text from commentController.text
|
||||
// Navigator.of(context).pop(); // Close the modal
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: layoutColor,
|
||||
// backgroundColor: widget.layoutColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Save',
|
||||
style: GoogleFonts.poppins(fontSize: 14, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_editorScrollController.dispose();
|
||||
_editorFocusNode.dispose();
|
||||
}
|
||||
|
||||
class TimeStampEmbed extends Embeddable {
|
||||
const TimeStampEmbed(String value) : super(timeStampType, value);
|
||||
|
||||
static const String timeStampType = 'timeStamp';
|
||||
|
||||
static TimeStampEmbed fromDocument(Document document) =>
|
||||
TimeStampEmbed(jsonEncode(document.toDelta().toJson()));
|
||||
|
||||
Document get document => Document.fromJson(jsonDecode(data));
|
||||
}
|
||||
|
||||
class TimeStampEmbedBuilder extends EmbedBuilder {
|
||||
@override
|
||||
String get key => 'timeStamp';
|
||||
|
||||
@override
|
||||
String toPlainText(Embed node) {
|
||||
return node.value.data;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, EmbedContext embedContext) {
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(Icons.access_time_rounded),
|
||||
Text(embedContext.node.value.data as String),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@ 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';
|
||||
@ -66,13 +67,15 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
String? bodyStringColor = await getBodyColor();
|
||||
|
||||
setState(() {
|
||||
layoutColor = layoutString != null
|
||||
? Color(int.parse(layoutString))
|
||||
: Colors.redAccent;
|
||||
layoutColor =
|
||||
layoutString != null
|
||||
? Color(int.parse(layoutString))
|
||||
: Colors.redAccent;
|
||||
|
||||
bodyColor = bodyStringColor != null
|
||||
? Color(int.parse(bodyStringColor))
|
||||
: Colors.white;
|
||||
bodyColor =
|
||||
bodyStringColor != null
|
||||
? Color(int.parse(bodyStringColor))
|
||||
: Colors.white;
|
||||
});
|
||||
}
|
||||
|
||||
@ -99,7 +102,7 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
|
||||
"created_by": null,
|
||||
"updated_by": null,
|
||||
"is_active": 1
|
||||
"is_active": 1,
|
||||
|
||||
// "org_id": orgId,
|
||||
// "created_by": userId,
|
||||
@ -144,75 +147,92 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
try {
|
||||
print("getUpdatedServices");
|
||||
|
||||
final result = await apiService.fetchOrganization();
|
||||
print("UUPdatedServices - $result");
|
||||
setState(() {
|
||||
selectedOrg = result;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final String? orgDataString = prefs.getString('org_data');
|
||||
|
||||
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";
|
||||
}
|
||||
if (orgDataString != null) {
|
||||
final Map<String, dynamic> orgData = jsonDecode(orgDataString);
|
||||
print("UUPdatedServices - $orgData");
|
||||
setState(() {
|
||||
selectedOrg = orgData;
|
||||
|
||||
_orgNameController.text = selectedOrg?['name'];
|
||||
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";
|
||||
}
|
||||
|
||||
layoutColor = selectedOrg?['layout_color'] != null
|
||||
? Color(int.parse(
|
||||
selectedOrg!['layout_color'].toString().replaceFirst('0x', ''),
|
||||
radix: 16))
|
||||
: Colors.white;
|
||||
_orgNameController.text = selectedOrg?['name'];
|
||||
|
||||
bodyColor = selectedOrg?['color'] != null
|
||||
? Color(int.parse(
|
||||
selectedOrg!['color'].toString().replaceFirst('0x', ''),
|
||||
radix: 16))
|
||||
: Colors.blue;
|
||||
layoutColor =
|
||||
selectedOrg?['layout_color'] != null
|
||||
? Color(
|
||||
int.parse(
|
||||
selectedOrg!['layout_color'].toString().replaceFirst(
|
||||
'0x',
|
||||
'',
|
||||
),
|
||||
radix: 16,
|
||||
),
|
||||
)
|
||||
: Colors.white;
|
||||
|
||||
// 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'];
|
||||
bodyColor =
|
||||
selectedOrg?['color'] != null
|
||||
? Color(
|
||||
int.parse(
|
||||
selectedOrg!['color'].toString().replaceFirst('0x', ''),
|
||||
radix: 16,
|
||||
),
|
||||
)
|
||||
: Colors.blue;
|
||||
|
||||
// Set selected service IDs
|
||||
// final services = selectedOrg?['services_ids'] as List<dynamic>? ?? [];
|
||||
// selectedServiceIds =
|
||||
// services.map((item) => item['service_id'].toString()).toList();
|
||||
//
|
||||
// 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'];
|
||||
|
||||
final servicesRaw = selectedOrg?['services_ids'];
|
||||
// Set selected service IDs
|
||||
// final services = selectedOrg?['services_ids'] as List<dynamic>? ?? [];
|
||||
// selectedServiceIds =
|
||||
// services.map((item) => item['service_id'].toString()).toList();
|
||||
//
|
||||
|
||||
List<dynamic> services;
|
||||
final servicesRaw = selectedOrg?['services_ids'];
|
||||
|
||||
if (servicesRaw is String) {
|
||||
try {
|
||||
services = jsonDecode(servicesRaw);
|
||||
} catch (e) {
|
||||
print('❌ Failed to decode services_ids: $e');
|
||||
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 = [];
|
||||
}
|
||||
} 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();
|
||||
});
|
||||
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();
|
||||
orgId = await getOrgId();
|
||||
|
||||
print("selectedOrg - $selectedOrg");
|
||||
print("mailConfig - $mailConfig");
|
||||
print("selectedOrg - $selectedOrg");
|
||||
print("mailConfig - $mailConfig");
|
||||
}
|
||||
|
||||
// final result = await apiService.fetchOrganization();
|
||||
} catch (e) {
|
||||
print('Error fetching updatedServices list: $e');
|
||||
}
|
||||
@ -222,10 +242,7 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
errorMessages.clear(); // Reset errors
|
||||
|
||||
// Required fields that must not be empty
|
||||
List<String> requiredFields = [
|
||||
"name",
|
||||
"description",
|
||||
];
|
||||
List<String> requiredFields = ["name", "description"];
|
||||
|
||||
// Check validation for each field
|
||||
for (String field in requiredFields) {
|
||||
@ -298,7 +315,26 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
print("✅ User submitted successfully!");
|
||||
print("📨 Response: ${response.body}");
|
||||
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('/listPlan');
|
||||
} else {
|
||||
print("❌ Submission failed. Status: ${response.statusCode}");
|
||||
@ -309,6 +345,27 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
} 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));
|
||||
print("✅ Updated org_data saved.");
|
||||
}
|
||||
|
||||
void handleSubmit() {
|
||||
print("HandleSubmiy - $orgData");
|
||||
createOrgData(orgData);
|
||||
@ -327,32 +384,38 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||
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))
|
||||
],
|
||||
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) {
|
||||
@ -380,20 +443,28 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(5),
|
||||
color: Colors.white,
|
||||
child: isDesktop
|
||||
? Row(
|
||||
padding: const EdgeInsets.all(5),
|
||||
color: Colors.white,
|
||||
child:
|
||||
isDesktop
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
// children: [Text("Button")],
|
||||
children:
|
||||
_buildSubmit(isDesktop, isViewMode, layoutColor),
|
||||
children: _buildSubmit(
|
||||
isDesktop,
|
||||
isViewMode,
|
||||
layoutColor,
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children:
|
||||
_buildSubmit(isDesktop, isViewMode, layoutColor),
|
||||
))
|
||||
children: _buildSubmit(
|
||||
isDesktop,
|
||||
isViewMode,
|
||||
layoutColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -402,8 +473,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
Widget buildOrgLayout(bool isDesktop) {
|
||||
Future<void> _pickImage() async {
|
||||
final picker = ImagePicker();
|
||||
final XFile? pickedFile =
|
||||
await picker.pickImage(source: ImageSource.gallery);
|
||||
final XFile? pickedFile = await picker.pickImage(
|
||||
source: ImageSource.gallery,
|
||||
);
|
||||
|
||||
if (pickedFile != null && kIsWeb) {
|
||||
try {
|
||||
@ -424,9 +496,10 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
// 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,
|
||||
height:
|
||||
isDesktop
|
||||
? MediaQuery.of(context).size.height * 0.98
|
||||
: MediaQuery.of(context).size.height,
|
||||
// decoration: BoxDecoration(
|
||||
// border: isDesktop
|
||||
// ? Border.all(
|
||||
@ -446,7 +519,11 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20, right: 20, bottom: 20, top: 5),
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
top: 5,
|
||||
),
|
||||
// height: MediaQuery.of(context).size.height * 0.8,
|
||||
color: Colors.white,
|
||||
child: Column(
|
||||
@ -463,7 +540,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
? "Update Organization"
|
||||
: "Create Organization",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 15, fontWeight: FontWeight.w500),
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -472,16 +551,19 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
color: Colors.white,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center, // now -> .center , old -> .start
|
||||
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)),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF212121),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
@ -496,7 +578,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
decoration: InputDecoration(
|
||||
hintText: "Enter Organization Name",
|
||||
hintStyle: GoogleFonts.poppins(
|
||||
fontSize: 14, color: Colors.grey),
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
floatingLabelBehavior:
|
||||
FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
@ -510,84 +594,87 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
Spacer(),
|
||||
GestureDetector(
|
||||
onTap: _pickImage,
|
||||
child: _imageBytes != null
|
||||
? ClipOval(
|
||||
child: Image.memory(
|
||||
_imageBytes!,
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
: selectedOrg?['logo'] != null
|
||||
? ClipRect(
|
||||
child: Image.network(
|
||||
selectedOrg!['logo'],
|
||||
width: 250, // increased
|
||||
height: 75, // increased
|
||||
|
||||
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),
|
||||
child:
|
||||
_imageBytes != null
|
||||
? ClipOval(
|
||||
child: Image.memory(
|
||||
_imageBytes!,
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
: selectedOrg?['logo'] != null
|
||||
? ClipRect(
|
||||
child: Image.network(
|
||||
selectedOrg!['logo'],
|
||||
width: 250, // increased
|
||||
height: 75, // increased
|
||||
|
||||
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,
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
|
||||
Text(
|
||||
"Services",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF212121)),
|
||||
),
|
||||
SizedBox(
|
||||
height: 10,
|
||||
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(),
|
||||
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,
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -595,9 +682,10 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
Text(
|
||||
"Choose Theme",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF212121)),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF212121),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
@ -606,99 +694,106 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
// 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(),
|
||||
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,
|
||||
),
|
||||
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)),
|
||||
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)
|
||||
// GestureDetector(
|
||||
// onTap: () {
|
||||
// setState(() {
|
||||
// showMail = !showMail;
|
||||
// });
|
||||
// },
|
||||
// child: Icon(
|
||||
// Icons.keyboard_arrow_down_outlined,
|
||||
// color: Color(0xFF114D8B),
|
||||
// size: 30,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: 10,
|
||||
// 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),
|
||||
),
|
||||
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
|
||||
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");
|
||||
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(),
|
||||
],
|
||||
))
|
||||
],
|
||||
)),
|
||||
mailConfig = updatedData;
|
||||
},
|
||||
)
|
||||
: CircularProgressIndicator(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// isDesktop
|
||||
// ? Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.end,
|
||||
@ -738,8 +833,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
|
||||
String serviceId = service['service_id'].toString();
|
||||
// bool isSelected = selectedServiceIds.contains(serviceId);
|
||||
bool isSelected =
|
||||
selectedServiceIds.any((item) => item["service_id"] == serviceId);
|
||||
bool isSelected = selectedServiceIds.any(
|
||||
(item) => item["service_id"] == serviceId,
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
@ -747,8 +843,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
String serviceId = service['service_id'].toString();
|
||||
|
||||
// Check if already selected
|
||||
int existingIndex = selectedServiceIds
|
||||
.indexWhere((item) => item["service_id"] == serviceId);
|
||||
int existingIndex = selectedServiceIds.indexWhere(
|
||||
(item) => item["service_id"] == serviceId,
|
||||
);
|
||||
|
||||
if (existingIndex != -1) {
|
||||
selectedServiceIds.removeAt(existingIndex);
|
||||
@ -757,54 +854,65 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Row(children: [
|
||||
iconUrl.isNotEmpty
|
||||
? Image.network(
|
||||
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));
|
||||
return Icon(
|
||||
fallbackIcon,
|
||||
size: 18,
|
||||
color:
|
||||
isSelected == name
|
||||
? Color(0xFF114D8B)
|
||||
: Color(0xFF475569),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Icon(fallbackIcon,
|
||||
: Icon(
|
||||
fallbackIcon,
|
||||
size: 18,
|
||||
color:
|
||||
isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569)),
|
||||
isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569),
|
||||
),
|
||||
|
||||
SizedBox(width: 2),
|
||||
SizedBox(width: 2),
|
||||
|
||||
Text(
|
||||
name,
|
||||
style: GoogleFonts.poppins(
|
||||
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,)),
|
||||
),
|
||||
isSelected == name ? FontWeight.bold : FontWeight.w500,
|
||||
),
|
||||
// fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)),
|
||||
),
|
||||
|
||||
SizedBox(width: 2),
|
||||
// if (selectedListOption == title && widget.isViewMode == false)
|
||||
Container(
|
||||
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),
|
||||
color: isSelected ? Colors.green : Colors.grey,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.check_circle,
|
||||
size: 10,
|
||||
color: isSelected ? Colors.green : Colors.grey,
|
||||
// color: Colors.grey,
|
||||
)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -836,25 +944,21 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
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),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: layoutColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: layoutColor ?? Colors.grey, width: 2),
|
||||
),
|
||||
onPressed: () {
|
||||
context.go('/listPlan');
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: GoogleFonts.poppins(fontSize: 12),
|
||||
)),
|
||||
SizedBox(
|
||||
width: 20,
|
||||
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
|
||||
@ -873,12 +977,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
onPressed: handleSubmit, // Disable when in view mode
|
||||
child: Text(
|
||||
"Submit",
|
||||
style: GoogleFonts.poppins(fontSize: 12),
|
||||
),
|
||||
child: Text("Submit", style: GoogleFonts.poppins(fontSize: 12)),
|
||||
),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -313,6 +313,8 @@ class _UserListScreenState extends State<UserListScreen> {
|
||||
(user['role_value']?.toLowerCase().contains(lowerQuery) ??
|
||||
false);
|
||||
}).toList();
|
||||
|
||||
currentPage = 0;
|
||||
});
|
||||
print("filteredPlans: $filteredUsers");
|
||||
}
|
||||
|
||||
@ -2,9 +2,10 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
// import 'package:flutter/rendering.dart';
|
||||
import 'dart:html' as html;
|
||||
import 'package:frontend/config/apiUrl.dart'; // 1 newly added
|
||||
import 'package:frontend/services/apiService.dart';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
@ -23,13 +24,14 @@ class MyApp extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
final ApiService apiService = ApiService();
|
||||
String? _authCode;
|
||||
String? userRole;
|
||||
bool _isAuthRedirect = false;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
SemanticsBinding.instance.ensureSemantics(); // ✅ Safe here
|
||||
// SemanticsBinding.instance.ensureSemantics(); // ✅ Safe here
|
||||
if (kIsWeb) {
|
||||
final uri = Uri.parse(html.window.location.href);
|
||||
if (uri.path == '/authredirection' &&
|
||||
@ -119,6 +121,7 @@ class _MyAppState extends State<MyApp> {
|
||||
print("userData11 - ${userData['role']}");
|
||||
print("userData12 - $userRole");
|
||||
}
|
||||
apiService.getOrganizationData();
|
||||
} catch (e) {
|
||||
print('Error decoding token: $e');
|
||||
}
|
||||
|
||||
@ -1,18 +1,13 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:frontend/config/apiUrl.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart'; // don't forget
|
||||
import '../services/apiService.dart';
|
||||
import '../utils/auth_utils.dart';
|
||||
|
||||
enum TabSelection {
|
||||
dashboard,
|
||||
allTrips,
|
||||
myTrips,
|
||||
myApprovals,
|
||||
allMenu,
|
||||
}
|
||||
enum TabSelection { dashboard, allTrips, myTrips, myApprovals, allMenu }
|
||||
|
||||
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
final bool isDesktop;
|
||||
@ -131,6 +126,64 @@ class _CustomAppBarState extends State<CustomAppBar> {
|
||||
}
|
||||
|
||||
Future<void> getOrganizationData() async {
|
||||
try {
|
||||
print("getUpdatedServices");
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final String? orgDataString = prefs.getString('org_data');
|
||||
|
||||
if (orgDataString != null) {
|
||||
// final result = await apiService.fetchOrganization();
|
||||
|
||||
final Map<String, dynamic> result = jsonDecode(orgDataString);
|
||||
print("UUPdatedServices - $result");
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
print("UUPdatedServices - $result");
|
||||
|
||||
setState(() {
|
||||
selectedOrg = result;
|
||||
|
||||
layoutColor =
|
||||
selectedOrg?['layout_color'] != null
|
||||
? Color(int.parse(selectedOrg!['layout_color']))
|
||||
: Colors.white;
|
||||
|
||||
bodyColor =
|
||||
selectedOrg?['color'] != null
|
||||
? Color(
|
||||
int.parse(
|
||||
selectedOrg!['color'].toString().replaceFirst('0x', ''),
|
||||
radix: 16,
|
||||
),
|
||||
)
|
||||
: Colors.blue;
|
||||
|
||||
String? rawLogoPath = selectedOrg?['logo'];
|
||||
if (rawLogoPath != null && rawLogoPath.contains('/assets')) {
|
||||
const baseUrl = apiUrl;
|
||||
|
||||
// const baseUrl = "https://apitest.tripapprovaltool.com";
|
||||
final assetPath = rawLogoPath.split('/assets').last;
|
||||
selectedOrg!['logo'] = "$baseUrl/assets$assetPath";
|
||||
}
|
||||
});
|
||||
|
||||
// Save to SharedPreferences
|
||||
await prefs.setString('layout_color', selectedOrg?['layout_color']);
|
||||
await prefs.setString('body_color', selectedOrg?['color']);
|
||||
await prefs.setString('body_color', selectedOrg?['plan_action']);
|
||||
|
||||
print(
|
||||
"Layout Color- ${selectedOrg?['layout_color']} - $layoutColor ---------- bodyColor - $bodyColor",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print("Error : $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getOrganizationData1() async {
|
||||
try {
|
||||
print("getUpdatedServices");
|
||||
|
||||
@ -142,15 +195,20 @@ class _CustomAppBarState extends State<CustomAppBar> {
|
||||
setState(() {
|
||||
selectedOrg = result;
|
||||
|
||||
layoutColor = selectedOrg?['layout_color'] != null
|
||||
? Color(int.parse(selectedOrg!['layout_color']))
|
||||
: Colors.white;
|
||||
layoutColor =
|
||||
selectedOrg?['layout_color'] != null
|
||||
? Color(int.parse(selectedOrg!['layout_color']))
|
||||
: Colors.white;
|
||||
|
||||
bodyColor = selectedOrg?['color'] != null
|
||||
? Color(int.parse(
|
||||
selectedOrg!['color'].toString().replaceFirst('0x', ''),
|
||||
radix: 16))
|
||||
: Colors.blue;
|
||||
bodyColor =
|
||||
selectedOrg?['color'] != null
|
||||
? Color(
|
||||
int.parse(
|
||||
selectedOrg!['color'].toString().replaceFirst('0x', ''),
|
||||
radix: 16,
|
||||
),
|
||||
)
|
||||
: Colors.blue;
|
||||
|
||||
String? rawLogoPath = selectedOrg?['logo'];
|
||||
if (rawLogoPath != null && rawLogoPath.contains('/assets')) {
|
||||
@ -166,22 +224,32 @@ class _CustomAppBarState extends State<CustomAppBar> {
|
||||
await prefs.setString('body_color', selectedOrg?['plan_action']);
|
||||
|
||||
print(
|
||||
"Layout Color- ${selectedOrg?['layout_color']} - $layoutColor ---------- bodyColor - $bodyColor");
|
||||
"Layout Color- ${selectedOrg?['layout_color']} - $layoutColor ---------- bodyColor - $bodyColor",
|
||||
);
|
||||
} catch (e) {
|
||||
print("Error : $e");
|
||||
}
|
||||
}
|
||||
|
||||
// void handleTabChange(TabSelection tab, String route) {
|
||||
// final currentUri =
|
||||
// GoRouterState.of(context).uri.toString(); // ✅ safer than `.location`
|
||||
// print("currentUri - $currentUri");
|
||||
//
|
||||
// if (currentUri != route) {
|
||||
// setState(() {
|
||||
// selectedTab = tab;
|
||||
// });
|
||||
// context.go(route);
|
||||
// }
|
||||
// }
|
||||
|
||||
void handleTabChange(TabSelection tab, String route) {
|
||||
final currentUri =
|
||||
GoRouterState.of(context).uri.toString(); // ✅ safer than `.location`
|
||||
print("currentUri - $currentUri");
|
||||
final currentUri = GoRouterState.of(context).uri.toString();
|
||||
|
||||
if (currentUri != route) {
|
||||
setState(() {
|
||||
selectedTab = tab;
|
||||
});
|
||||
context.go(route);
|
||||
context.go(route); // 🔄 Let navigation happen
|
||||
// The tab selection will automatically be updated by didChangeDependencies
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,248 +293,266 @@ class _CustomAppBarState extends State<CustomAppBar> {
|
||||
|
||||
titleSpacing: 0,
|
||||
|
||||
title: !widget.isDesktop
|
||||
? Text('')
|
||||
: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width * 0.05),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10),
|
||||
title:
|
||||
!widget.isDesktop
|
||||
? Text('')
|
||||
: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width * 0.05,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
|
||||
child: selectedOrg?['logo'] != null
|
||||
? SizedBox(
|
||||
height: 60,
|
||||
child: ClipRect(
|
||||
child: Image.network(
|
||||
selectedOrg!['logo'],
|
||||
width: 130, //130
|
||||
height: 80, //80
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: Colors.redAccent,
|
||||
child: Icon(Icons.error, size: 10),
|
||||
);
|
||||
},
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10),
|
||||
child:
|
||||
selectedOrg?['logo'] != null
|
||||
? SizedBox(
|
||||
height: 60,
|
||||
child: ClipRect(
|
||||
child: Image.network(
|
||||
selectedOrg!['logo'],
|
||||
width: 130, //130
|
||||
height: 80, //80
|
||||
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.white,
|
||||
child: Icon(
|
||||
Icons.add_a_photo,
|
||||
size: 10,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
))
|
||||
: const CircleAvatar(
|
||||
radius: 20,
|
||||
// backgroundColor: Colors.white,
|
||||
child: Icon(
|
||||
Icons.add_a_photo,
|
||||
size: 10,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.18,
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width * 0.35,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (userData?["role"] == "Org Admin" ||
|
||||
userData?["role"] == "Travel Admin")
|
||||
buildNavItem(
|
||||
"Dashboard",
|
||||
() => handleTabChange(
|
||||
TabSelection.dashboard, '/StatusDashboard'),
|
||||
layoutColor!,
|
||||
isSelected: selectedTab == TabSelection.dashboard,
|
||||
icon: Icons.dashboard,
|
||||
// icon: Icons.insights_outlined,
|
||||
),
|
||||
if (userData?["role"] == "Org Admin" ||
|
||||
userData?["role"] == "Travel Admin")
|
||||
const SizedBox(width: 20),
|
||||
if (userData?["role"] == "Org Admin" ||
|
||||
userData?["role"] == "Travel Admin")
|
||||
buildNavItem(
|
||||
"All Trips",
|
||||
() => handleTabChange(
|
||||
TabSelection.allTrips, '/listAllPlan'),
|
||||
layoutColor!,
|
||||
isSelected: selectedTab == TabSelection.allTrips,
|
||||
icon: Icons.format_list_bulleted_rounded,
|
||||
// icon: Icons.insights_outlined,
|
||||
),
|
||||
if (userDetails["role"] != "Travel Agent")
|
||||
const SizedBox(width: 20),
|
||||
if (userData?["role"] == "Travel Agent")
|
||||
buildNavItem(
|
||||
"Trips",
|
||||
() => handleTabChange(
|
||||
TabSelection.myTrips, '/listTravelAgentPlan'),
|
||||
layoutColor!,
|
||||
// () => context.go('/listTravelAgentPlan'),
|
||||
isSelected: selectedTab == TabSelection.myTrips,
|
||||
icon: Icons.shopping_bag_outlined,
|
||||
// icon: Icons.request_page_outlined,
|
||||
),
|
||||
if (userData?["role"] != "Travel Agent") // for others
|
||||
buildNavItem(
|
||||
"My Trips",
|
||||
() => handleTabChange(
|
||||
TabSelection.myTrips, '/listPlan'),
|
||||
layoutColor!,
|
||||
// () => context.go('/listPlan'),
|
||||
isSelected: selectedTab == TabSelection.myTrips,
|
||||
icon: Icons.shopping_bag_outlined,
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
if (userData?["role"] != "Travel Agent")
|
||||
buildNavItem(
|
||||
"My Approvals",
|
||||
|
||||
() => handleTabChange(
|
||||
TabSelection.myApprovals, '/ApprovalList'),
|
||||
layoutColor!,
|
||||
// () => context.go('/ApprovalList'),
|
||||
isSelected: selectedTab == TabSelection.myApprovals,
|
||||
icon: Icons.verified_outlined,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
],
|
||||
SizedBox(width: MediaQuery.of(context).size.width * 0.18),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width * 0.35,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (userData?["role"] == "Org Admin" ||
|
||||
userData?["role"] == "Travel Admin")
|
||||
buildNavItem(
|
||||
"Dashboard",
|
||||
() => handleTabChange(
|
||||
TabSelection.dashboard,
|
||||
'/StatusDashboard',
|
||||
),
|
||||
layoutColor!,
|
||||
isSelected: selectedTab == TabSelection.dashboard,
|
||||
icon: Icons.dashboard,
|
||||
// icon: Icons.insights_outlined,
|
||||
),
|
||||
if (userData?["role"] == "Org Admin" ||
|
||||
userData?["role"] == "Travel Admin")
|
||||
const SizedBox(width: 20),
|
||||
if (userData?["role"] == "Org Admin" ||
|
||||
userData?["role"] == "Travel Admin")
|
||||
buildNavItem(
|
||||
"All Trips",
|
||||
() => handleTabChange(
|
||||
TabSelection.allTrips,
|
||||
'/listAllPlan',
|
||||
),
|
||||
layoutColor!,
|
||||
isSelected: selectedTab == TabSelection.allTrips,
|
||||
icon: Icons.format_list_bulleted_rounded,
|
||||
// icon: Icons.insights_outlined,
|
||||
),
|
||||
if (userDetails["role"] != "Travel Agent")
|
||||
const SizedBox(width: 20),
|
||||
if (userData?["role"] == "Travel Agent")
|
||||
buildNavItem(
|
||||
"Trips",
|
||||
() => handleTabChange(
|
||||
TabSelection.myTrips,
|
||||
'/listTravelAgentPlan',
|
||||
),
|
||||
layoutColor!,
|
||||
// () => context.go('/listTravelAgentPlan'),
|
||||
isSelected: selectedTab == TabSelection.myTrips,
|
||||
icon: Icons.shopping_bag_outlined,
|
||||
// icon: Icons.request_page_outlined,
|
||||
),
|
||||
if (userData?["role"] != "Travel Agent") // for others
|
||||
buildNavItem(
|
||||
"My Trips",
|
||||
() => handleTabChange(
|
||||
TabSelection.myTrips,
|
||||
'/listPlan',
|
||||
),
|
||||
layoutColor!,
|
||||
// () => context.go('/listPlan'),
|
||||
isSelected: selectedTab == TabSelection.myTrips,
|
||||
icon: Icons.shopping_bag_outlined,
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
if (userData?["role"] != "Travel Agent")
|
||||
buildNavItem(
|
||||
"My Approvals",
|
||||
|
||||
() => handleTabChange(
|
||||
TabSelection.myApprovals,
|
||||
'/ApprovalList',
|
||||
),
|
||||
layoutColor!,
|
||||
// () => context.go('/ApprovalList'),
|
||||
isSelected:
|
||||
selectedTab == TabSelection.myApprovals,
|
||||
icon: Icons.verified_outlined,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width * 0.05),
|
||||
horizontal: MediaQuery.of(context).size.width * 0.05,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// if (userData?["role"] != "User")
|
||||
Builder(
|
||||
builder: (context) => PopupMenuButton<String>(
|
||||
color: Colors.white,
|
||||
padding: EdgeInsets.zero,
|
||||
offset: const Offset(0, 50), // 👈 shift it 50 pixels down
|
||||
onSelected: (String value) {
|
||||
switch (value) {
|
||||
case '/OrganizationSettings':
|
||||
context.go('/OrganizationSettings');
|
||||
break;
|
||||
// case '/OrganizationSetup':
|
||||
// context.go('/OrganizationSetup');
|
||||
// break;
|
||||
case '/listUser':
|
||||
context.go('/listUser');
|
||||
break;
|
||||
// case '/group':
|
||||
// context.go('/group');
|
||||
// break;
|
||||
// case '/department':
|
||||
// context.go('/department');
|
||||
// break;
|
||||
// case '/PolicyList':
|
||||
// context.go('/PolicyList');
|
||||
// case '/getPerdiem':
|
||||
// context.go('/getPerdiem');
|
||||
// case '/templateList':
|
||||
// context.go('/templateList');
|
||||
// case '/template':
|
||||
// context.go('/template');
|
||||
builder:
|
||||
(context) => PopupMenuButton<String>(
|
||||
color: Colors.white,
|
||||
padding: EdgeInsets.zero,
|
||||
offset: const Offset(0, 50), // 👈 shift it 50 pixels down
|
||||
onSelected: (String value) {
|
||||
switch (value) {
|
||||
case '/OrganizationSettings':
|
||||
context.go('/OrganizationSettings');
|
||||
break;
|
||||
// case '/OrganizationSetup':
|
||||
// context.go('/OrganizationSetup');
|
||||
// break;
|
||||
case '/listUser':
|
||||
context.go('/listUser');
|
||||
break;
|
||||
// case '/group':
|
||||
// context.go('/group');
|
||||
// break;
|
||||
// case '/department':
|
||||
// context.go('/department');
|
||||
// break;
|
||||
// case '/PolicyList':
|
||||
// context.go('/PolicyList');
|
||||
// case '/getPerdiem':
|
||||
// context.go('/getPerdiem');
|
||||
// case '/templateList':
|
||||
// context.go('/templateList');
|
||||
// case '/template':
|
||||
// context.go('/template');
|
||||
|
||||
case '/CreateUserDetails':
|
||||
context.go(
|
||||
"/CreateUserDetails",
|
||||
extra: {
|
||||
"selectedUser": profileUserDetails,
|
||||
"isEditProfile": true,
|
||||
"isViewMode": false,
|
||||
},
|
||||
);
|
||||
case '/logout':
|
||||
context.go('/');
|
||||
break;
|
||||
}
|
||||
},
|
||||
case '/CreateUserDetails':
|
||||
context.go(
|
||||
"/CreateUserDetails",
|
||||
extra: {
|
||||
"selectedUser": profileUserDetails,
|
||||
"isEditProfile": true,
|
||||
"isViewMode": false,
|
||||
},
|
||||
);
|
||||
case '/logout':
|
||||
context.go('/');
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// itemBuilder: (BuildContext context) =>
|
||||
// menuItems.map(buildMenuItem).toList(),
|
||||
// itemBuilder: (BuildContext context) =>
|
||||
// menuItems.map(buildMenuItem).toList(),
|
||||
itemBuilder: (BuildContext context) {
|
||||
// final isUser = userData?["role"] == "User";
|
||||
final role = userData?["role"];
|
||||
List<Map<String, dynamic>> filteredItems;
|
||||
|
||||
itemBuilder: (BuildContext context) {
|
||||
// final isUser = userData?["role"] == "User";
|
||||
final role = userData?["role"];
|
||||
List<Map<String, dynamic>> filteredItems;
|
||||
if (role == "User") {
|
||||
filteredItems =
|
||||
menuItems
|
||||
.where(
|
||||
(item) =>
|
||||
item['value'] == '/CreateUserDetails' ||
|
||||
item['value'] == '/logout',
|
||||
)
|
||||
.toList();
|
||||
} else if (role == "Travel Agent") {
|
||||
filteredItems =
|
||||
menuItems
|
||||
.where((item) => item['value'] == '/logout')
|
||||
.toList();
|
||||
} else {
|
||||
filteredItems = menuItems;
|
||||
}
|
||||
|
||||
if (role == "User") {
|
||||
filteredItems = menuItems
|
||||
.where((item) =>
|
||||
item['value'] == '/CreateUserDetails' ||
|
||||
item['value'] == '/logout')
|
||||
.toList();
|
||||
} else if (role == "Travel Agent") {
|
||||
filteredItems = menuItems
|
||||
.where((item) => item['value'] == '/logout')
|
||||
.toList();
|
||||
} else {
|
||||
filteredItems = menuItems;
|
||||
}
|
||||
// Create a new list starting with role display and divider
|
||||
return [
|
||||
PopupMenuItem<String>(
|
||||
enabled: false, // ❌ Not clickable
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
userData?["role"] ?? '',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
const Divider(), // 👈 Divider after role
|
||||
],
|
||||
),
|
||||
),
|
||||
...filteredItems
|
||||
.map(buildMenuItem)
|
||||
.toList(), // 👈 then normal items
|
||||
];
|
||||
},
|
||||
|
||||
// Create a new list starting with role display and divider
|
||||
return [
|
||||
PopupMenuItem<String>(
|
||||
enabled: false, // ❌ Not clickable
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
userData?["role"] ?? '',
|
||||
userData?["name"] ?? "N/A",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black,
|
||||
),
|
||||
// style: const TextStyle(
|
||||
// fontSize: 14,
|
||||
// fontWeight: FontWeight.w500,
|
||||
// fontFamily: "Roboto",
|
||||
// color: Colors.black,
|
||||
// ),
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_drop_down,
|
||||
size: 20,
|
||||
color: Colors.black87,
|
||||
),
|
||||
const Divider(), // 👈 Divider after role
|
||||
],
|
||||
),
|
||||
),
|
||||
...filteredItems
|
||||
.map(buildMenuItem)
|
||||
.toList(), // 👈 then normal items
|
||||
];
|
||||
},
|
||||
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
userData?["name"] ?? "N/A",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black,
|
||||
),
|
||||
// style: const TextStyle(
|
||||
// fontSize: 14,
|
||||
// fontWeight: FontWeight.w500,
|
||||
// fontFamily: "Roboto",
|
||||
// color: Colors.black,
|
||||
// ),
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_drop_down,
|
||||
size: 20,
|
||||
color: Colors.black87,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
@ -497,12 +583,12 @@ final List<Map<String, dynamic>> menuItems = [
|
||||
{
|
||||
'value': '/OrganizationSettings',
|
||||
'icon': Icons.business,
|
||||
'label': 'Org Management'
|
||||
'label': 'Org Management',
|
||||
},
|
||||
{
|
||||
'value': '/listUser',
|
||||
'icon': Icons.manage_accounts,
|
||||
'label': 'User Management'
|
||||
'label': 'User Management',
|
||||
},
|
||||
|
||||
// {'value': '/group', 'icon': Icons.group, 'label': 'Group'},
|
||||
@ -518,7 +604,7 @@ final List<Map<String, dynamic>> menuItems = [
|
||||
{
|
||||
'value': '/CreateUserDetails',
|
||||
'icon': Icons.account_circle,
|
||||
'label': 'My Profile'
|
||||
'label': 'My Profile',
|
||||
},
|
||||
{'value': '/logout', 'icon': Icons.login_outlined, 'label': 'Logout'},
|
||||
];
|
||||
@ -527,12 +613,16 @@ PopupMenuItem<String> buildMenuItem(Map<String, dynamic> item) {
|
||||
return PopupMenuItem<String>(
|
||||
height: 40, // 👈 reduce PopupMenuItem height
|
||||
value: item['value'],
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 12), // 👈 control left-right spacing
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
), // 👈 control left-right spacing
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(item['icon'],
|
||||
size: 18, color: Colors.black87), // 👈 smaller, cleaner icon
|
||||
Icon(
|
||||
item['icon'],
|
||||
size: 18,
|
||||
color: Colors.black87,
|
||||
), // 👈 smaller, cleaner icon
|
||||
SizedBox(width: 10), // 👈 small space between icon and text
|
||||
Text(
|
||||
item['label'],
|
||||
@ -547,8 +637,13 @@ PopupMenuItem<String> buildMenuItem(Map<String, dynamic> item) {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildNavItem(String label, VoidCallback onTap, Color? layoutColor,
|
||||
{bool isSelected = true, IconData? icon}) {
|
||||
Widget buildNavItem(
|
||||
String label,
|
||||
VoidCallback onTap,
|
||||
Color? layoutColor, {
|
||||
bool isSelected = true,
|
||||
IconData? icon,
|
||||
}) {
|
||||
final effectiveColor =
|
||||
isSelected ? (layoutColor ?? Colors.blue) : Colors.black;
|
||||
|
||||
@ -587,9 +682,10 @@ Widget buildNavItem(String label, VoidCallback onTap, Color? layoutColor,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
height: 2,
|
||||
width: isSelected
|
||||
? 50
|
||||
: 0, // Animate width (make sure isSelected changes)
|
||||
width:
|
||||
isSelected
|
||||
? 50
|
||||
: 0, // Animate width (make sure isSelected changes)
|
||||
color: effectiveColor,
|
||||
),
|
||||
),
|
||||
|
||||
@ -84,39 +84,53 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
try {
|
||||
print("getUpdatedServices");
|
||||
|
||||
final result = await apiService.fetchOrganization();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
print("UUPdatedServices - $result");
|
||||
final String? orgDataString = prefs.getString('org_data');
|
||||
|
||||
setState(() {
|
||||
selectedOrg = result;
|
||||
if (orgDataString != null) {
|
||||
// final result = await apiService.fetchOrganization();
|
||||
|
||||
layoutColor = selectedOrg?['layout_color'] != null
|
||||
? Color(int.parse(selectedOrg!['layout_color']))
|
||||
: Colors.white;
|
||||
final Map<String, dynamic> result = jsonDecode(orgDataString);
|
||||
print("UUPdatedServices - $result");
|
||||
|
||||
bodyColor = selectedOrg?['color'] != null
|
||||
? Color(int.parse(
|
||||
selectedOrg!['color'].toString().replaceFirst('0x', ''),
|
||||
radix: 16))
|
||||
: Colors.blue;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
print("UUPdatedServices - $result");
|
||||
|
||||
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";
|
||||
}
|
||||
});
|
||||
setState(() {
|
||||
selectedOrg = result;
|
||||
|
||||
// Save to SharedPreferences
|
||||
await prefs.setString('layout_color', selectedOrg?['layout_color']);
|
||||
await prefs.setString('body_color', selectedOrg?['color']);
|
||||
await prefs.setString('body_color', selectedOrg?['plan_action']);
|
||||
layoutColor =
|
||||
selectedOrg?['layout_color'] != null
|
||||
? Color(int.parse(selectedOrg!['layout_color']))
|
||||
: Colors.white;
|
||||
|
||||
print(
|
||||
"Layout Color- ${selectedOrg?['layout_color']} - $layoutColor ---------- bodyColor - $bodyColor");
|
||||
bodyColor =
|
||||
selectedOrg?['color'] != null
|
||||
? Color(
|
||||
int.parse(
|
||||
selectedOrg!['color'].toString().replaceFirst('0x', ''),
|
||||
radix: 16,
|
||||
),
|
||||
)
|
||||
: Colors.blue;
|
||||
|
||||
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";
|
||||
}
|
||||
});
|
||||
|
||||
// Save to SharedPreferences
|
||||
await prefs.setString('layout_color', selectedOrg?['layout_color']);
|
||||
await prefs.setString('body_color', selectedOrg?['color']);
|
||||
await prefs.setString('body_color', selectedOrg?['plan_action']);
|
||||
|
||||
print(
|
||||
"Layout Color- ${selectedOrg?['layout_color']} - $layoutColor ---------- bodyColor - $bodyColor",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print("Error : $e");
|
||||
}
|
||||
@ -126,7 +140,6 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
Widget build(BuildContext context) {
|
||||
Widget drawerContent = Container(
|
||||
// color: Colors.white,
|
||||
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(18),
|
||||
child: Column(
|
||||
@ -141,57 +154,77 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
children: [
|
||||
selectedOrg?['logo'] != null
|
||||
? ClipRect(
|
||||
child: Image.network(
|
||||
selectedOrg!['logo'],
|
||||
width: 100,
|
||||
height: 50,
|
||||
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.white,
|
||||
child: Icon(
|
||||
Icons.add_a_photo,
|
||||
size: 10,
|
||||
color: Colors.grey,
|
||||
),
|
||||
child: Image.network(
|
||||
selectedOrg!['logo'],
|
||||
width: 100,
|
||||
height: 50,
|
||||
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.white,
|
||||
child: Icon(
|
||||
Icons.add_a_photo,
|
||||
size: 10,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// _buildDrawerItem(context, Icons.home_outlined, 'Home', '/home'),
|
||||
if (userData?["role"] == "Org Admin" ||
|
||||
userData?["role"] == "Travel Admin")
|
||||
_buildDrawerItem(
|
||||
context,
|
||||
Icons.dashboard,
|
||||
'Dashboard',
|
||||
'/StatusDashboard',
|
||||
),
|
||||
|
||||
if (userData?["role"] == "Org Admin" ||
|
||||
userData?["role"] == "Travel Admin")
|
||||
_buildDrawerItem(context, Icons.dashboard, 'Dashboard',
|
||||
'/StatusDashboard'),
|
||||
|
||||
if (userData?["role"] == "Org Admin" ||
|
||||
userData?["role"] == "Travel Admin")
|
||||
_buildDrawerItem(context, Icons.insights_outlined, 'All Trips',
|
||||
'/listAllPlan'),
|
||||
_buildDrawerItem(
|
||||
context,
|
||||
Icons.insights_outlined,
|
||||
'All Trips',
|
||||
'/listAllPlan',
|
||||
),
|
||||
|
||||
if (userDetails["role"] == "Travel Agent")
|
||||
_buildDrawerItem(context, Icons.assessment_outlined,
|
||||
'My Approvals', '/listTravelAgentPlan'),
|
||||
_buildDrawerItem(
|
||||
context,
|
||||
Icons.assessment_outlined,
|
||||
'My Approvals',
|
||||
'/listTravelAgentPlan',
|
||||
),
|
||||
|
||||
if (userDetails["role"] != "Travel Agent")
|
||||
_buildDrawerItem(context, Icons.request_page_outlined, 'My Trips',
|
||||
'/listPlan'),
|
||||
_buildDrawerItem(
|
||||
context,
|
||||
Icons.request_page_outlined,
|
||||
'My Trips',
|
||||
'/listPlan',
|
||||
),
|
||||
|
||||
if (userDetails["role"] != "Travel Agent")
|
||||
_buildDrawerItem(context, Icons.assessment_outlined,
|
||||
'My Approvals', '/ApprovalList'),
|
||||
_buildDrawerItem(
|
||||
context,
|
||||
Icons.assessment_outlined,
|
||||
'My Approvals',
|
||||
'/ApprovalList',
|
||||
),
|
||||
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.5),
|
||||
Container(
|
||||
@ -206,8 +239,10 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
children: [
|
||||
Text(
|
||||
"Powered by",
|
||||
style:
|
||||
TextStyle(fontSize: 11, color: Color(0xFF212121)),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Color(0xFF212121),
|
||||
),
|
||||
),
|
||||
Image.asset(
|
||||
'assets/images/login/logoNew.jpg',
|
||||
@ -226,7 +261,8 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
);
|
||||
|
||||
return Drawer(
|
||||
child: ListView(padding: EdgeInsets.zero, children: [drawerContent]));
|
||||
child: ListView(padding: EdgeInsets.zero, children: [drawerContent]),
|
||||
);
|
||||
|
||||
// if (widget.isDesktop) {
|
||||
// // Sidebar for Desktop (always visible)**
|
||||
@ -244,7 +280,11 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
|
||||
/// **Reusable Drawer Item**
|
||||
Widget _buildDrawerItem(
|
||||
BuildContext context, IconData icon, String title, String route) {
|
||||
BuildContext context,
|
||||
IconData icon,
|
||||
String title,
|
||||
String route,
|
||||
) {
|
||||
String selectedRoute = GoRouterState.of(context).uri.toString();
|
||||
|
||||
// return Container(
|
||||
@ -287,36 +327,34 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
return Material(
|
||||
color: selectedRoute == route ? bodyColor : Colors.transparent,
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
),
|
||||
title: Text(
|
||||
title,
|
||||
leading: Icon(icon, size: 20),
|
||||
title: Text(
|
||||
title,
|
||||
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF475569),
|
||||
),
|
||||
// style: TextStyle(
|
||||
// fontSize: 12,
|
||||
// fontWeight: FontWeight.w600,
|
||||
// color: Color(0xFF475569),
|
||||
// fontFamily: "Archivo"),
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF475569),
|
||||
),
|
||||
// tileColor: selectedRoute == route ? Colors.blue.shade50 : null,
|
||||
// style: TextStyle(
|
||||
// fontSize: 12,
|
||||
// fontWeight: FontWeight.w600,
|
||||
// color: Color(0xFF475569),
|
||||
// fontFamily: "Archivo"),
|
||||
),
|
||||
|
||||
onTap: () async {
|
||||
if (route == '/') {
|
||||
// Handle logout separately
|
||||
final pref = await SharedPreferences.getInstance();
|
||||
await pref.clear(); // Clear stored token or session data
|
||||
context.go("/"); // Redirect to login instead of home
|
||||
} else {
|
||||
context.go(route);
|
||||
}
|
||||
}),
|
||||
// tileColor: selectedRoute == route ? Colors.blue.shade50 : null,
|
||||
onTap: () async {
|
||||
if (route == '/') {
|
||||
// Handle logout separately
|
||||
final pref = await SharedPreferences.getInstance();
|
||||
await pref.clear(); // Clear stored token or session data
|
||||
context.go("/"); // Redirect to login instead of home
|
||||
} else {
|
||||
context.go(route);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -338,11 +376,7 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
child: ExpansionTile(
|
||||
tilePadding: EdgeInsets.symmetric(horizontal: 16),
|
||||
// childrenPadding: EdgeInsets.only(left: 36),
|
||||
leading: Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: Color(0xFF475569),
|
||||
),
|
||||
leading: Icon(icon, size: 20, color: Color(0xFF475569)),
|
||||
title: Row(
|
||||
children: [
|
||||
// You could manually build this instead of using `leading`, but it's simpler here
|
||||
@ -378,11 +412,7 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 44.0, vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.circle_rounded,
|
||||
color: Color(0xFF475569),
|
||||
size: 6,
|
||||
),
|
||||
Icon(Icons.circle_rounded, color: Color(0xFF475569), size: 6),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
@ -399,20 +429,22 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExpandableItem1(BuildContext context, IconData icon,
|
||||
String title, List<Widget> children) {
|
||||
Widget _buildExpandableItem1(
|
||||
BuildContext context,
|
||||
IconData icon,
|
||||
String title,
|
||||
List<Widget> children,
|
||||
) {
|
||||
return ExpansionTile(
|
||||
leading: Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
),
|
||||
leading: Icon(icon, size: 20),
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF475569),
|
||||
fontFamily: "Archivo"),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF475569),
|
||||
fontFamily: "Archivo",
|
||||
),
|
||||
),
|
||||
collapsedBackgroundColor: Colors.transparent,
|
||||
shape: const Border(), // Removes top and bottom dividers
|
||||
@ -423,20 +455,20 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
}
|
||||
|
||||
Widget _buildSubDrawerItem1(
|
||||
BuildContext context, String title, String route) {
|
||||
BuildContext context,
|
||||
String title,
|
||||
String route,
|
||||
) {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.circle_rounded,
|
||||
color: Color(0xFF475569),
|
||||
size: 8,
|
||||
),
|
||||
leading: Icon(Icons.circle_rounded, color: Color(0xFF475569), size: 8),
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFF475569),
|
||||
fontFamily: "Archivo"),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFF475569),
|
||||
fontFamily: "Archivo",
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
context.go(route);
|
||||
|
||||
@ -16,6 +16,7 @@ import 'package:frontend/Screens/userManagement/create_user/create_user1.dart';
|
||||
import 'package:frontend/Screens/userManagement/user_List.dart';
|
||||
import 'package:frontend/routes/organizationSetting.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
|
||||
import '../Screens/allTrips/list_all_plans.dart';
|
||||
import '../Screens/allTrips/travel_agent_list.dart';
|
||||
@ -23,6 +24,7 @@ import '../Screens/approvals/approval_list.dart';
|
||||
import '../Screens/group/group.dart';
|
||||
import '../Screens/group/groupList.dart';
|
||||
import '../Screens/myTemplates/template.dart';
|
||||
import '../Screens/myTemplates/templateForex.dart';
|
||||
import '../Screens/myTemplates/templateTest.dart';
|
||||
import '../Screens/userManagement/create_user/create_user.dart';
|
||||
import '../Screens/department/department_list.dart';
|
||||
@ -30,96 +32,213 @@ import '../Screens/costCenter/costCenter_list.dart';
|
||||
import '../Screens/dashboard/status_dashboard.dart';
|
||||
import '../Screens/hotels/hotels_list.dart';
|
||||
import '../Screens/traveller/travellerList.dart';
|
||||
import 'mainLayout.dart';
|
||||
|
||||
final GoRouter router = GoRouter(
|
||||
routes: [
|
||||
// Public routes without app bar
|
||||
GoRoute(path: '/', builder: (context, state) => LoginPage()),
|
||||
// GoRoute(
|
||||
// path: '/authredirection',
|
||||
// builder: (context, state) {
|
||||
// final code = state.uri.queryParameters['code'];
|
||||
// return MicrosoftPage(code: code);
|
||||
// },
|
||||
// ),
|
||||
GoRoute(path: '/home', builder: (context, state) => HomePage()),
|
||||
GoRoute(path: '/listAllPlan', builder: (context, state) => ListAllPlans()),
|
||||
GoRoute(
|
||||
path: '/listTravelAgentPlan',
|
||||
builder: (context, state) => TravelAgentListPlans(),
|
||||
),
|
||||
GoRoute(path: '/listPlan', builder: (context, state) => ListPlans()),
|
||||
GoRoute(path: '/createPlan', builder: (context, state) => CreatePlan()),
|
||||
GoRoute(path: '/allTrips/trips', builder: (context, state) => CreatePlan()),
|
||||
GoRoute(path: '/approver/plans', builder: (context, state) => CreatePlan()),
|
||||
GoRoute(path: '/listUser', builder: (context, state) => UserListScreen()),
|
||||
GoRoute(
|
||||
path: '/CreateUserDetails',
|
||||
builder: (context, state) => CreateUserFormDetials(),
|
||||
// builder: (context, state) {
|
||||
// final userParam = state.uri.queryParameters['user'];
|
||||
//
|
||||
// final isEditProfile =
|
||||
// state.uri.queryParameters['isEditProfile'] == 'true';
|
||||
// final isViewMode = state.uri.queryParameters['isViewMode'] == 'true';
|
||||
//
|
||||
// final user = userParam != null
|
||||
// ? jsonDecode(Uri.decodeComponent(userParam))
|
||||
// : null;
|
||||
//
|
||||
// return CreateUserForm(
|
||||
// apiselectedUser: user,
|
||||
// isEditProfile: isEditProfile,
|
||||
// isViewMode: isViewMode,
|
||||
// );
|
||||
// }
|
||||
),
|
||||
GoRoute(
|
||||
path: '/Policy',
|
||||
// builder: (context, state) => Policy(),
|
||||
pageBuilder:
|
||||
(context, state) => MaterialPage(child: Policy.fromState(state)),
|
||||
),
|
||||
GoRoute(path: '/PolicyList', builder: (context, state) => PolicyList()),
|
||||
GoRoute(
|
||||
path: '/OrganizationSetup',
|
||||
builder: (context, state) => OrgSetUp(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/OrganizationSettings',
|
||||
builder: (context, state) => OrganizationSetting(),
|
||||
),
|
||||
GoRoute(path: '/group', builder: (context, state) => GroupList()),
|
||||
GoRoute(path: '/getPerdiem', builder: (context, state) => ForexDataList()),
|
||||
GoRoute(
|
||||
path: '/templateList',
|
||||
builder: (context, state) => TemplatesList(),
|
||||
),
|
||||
// GoRoute(
|
||||
// path: '/template',
|
||||
// builder: (context, state) => MyHomePage(),
|
||||
// ),
|
||||
GoRoute(
|
||||
path: '/template',
|
||||
// builder: (context, state) => Template(),
|
||||
pageBuilder:
|
||||
(context, state) => MaterialPage(child: Template.fromState(state)),
|
||||
),
|
||||
GoRoute(path: '/approvallist', builder: (context, state) => ApprovalList()),
|
||||
GoRoute(path: '/department', builder: (context, state) => DepartmentList()),
|
||||
GoRoute(path: '/costcenter', builder: (context, state) => CostCenterList()),
|
||||
GoRoute(path: '/hotels', builder: (context, state) => HotelsDataList()),
|
||||
GoRoute(
|
||||
path: '/statusdashboard',
|
||||
builder: (context, state) => StatusDashboard(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/traveller',
|
||||
builder: (context, state) => TravellerList(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/CreateGroup',
|
||||
pageBuilder:
|
||||
(context, state) => MaterialPage(child: Group.fromState(state)),
|
||||
|
||||
// Routes that share the app bar and layout (nested routes)
|
||||
ShellRoute(
|
||||
builder: (context, state, child) {
|
||||
// Use ResponsiveBuilder here to detect isDesktop and pass to MainLayout
|
||||
return child;
|
||||
// return ResponsiveBuilder(
|
||||
// builder: (context, sizingInfo) {
|
||||
// bool isDesktop =
|
||||
// sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||
//
|
||||
// return MainLayout(isDesktop: isDesktop, child: child);
|
||||
// },
|
||||
// );
|
||||
},
|
||||
routes: [
|
||||
GoRoute(path: '/home', builder: (context, state) => HomePage()),
|
||||
GoRoute(
|
||||
path: '/listAllPlan',
|
||||
builder: (context, state) => ListAllPlans(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/listTravelAgentPlan',
|
||||
builder: (context, state) => TravelAgentListPlans(),
|
||||
),
|
||||
GoRoute(path: '/listPlan', builder: (context, state) => ListPlans()),
|
||||
GoRoute(path: '/createPlan', builder: (context, state) => CreatePlan()),
|
||||
GoRoute(
|
||||
path: '/allTrips/trips',
|
||||
builder: (context, state) => CreatePlan(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/approver/plans',
|
||||
builder: (context, state) => CreatePlan(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/listUser',
|
||||
builder: (context, state) => UserListScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/CreateUserDetails',
|
||||
builder: (context, state) => CreateUserFormDetials(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/Policy',
|
||||
pageBuilder:
|
||||
(context, state) => MaterialPage(child: Policy.fromState(state)),
|
||||
),
|
||||
GoRoute(path: '/PolicyList', builder: (context, state) => PolicyList()),
|
||||
GoRoute(
|
||||
path: '/OrganizationSetup',
|
||||
builder: (context, state) => OrgSetUp(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/OrganizationSettings',
|
||||
builder: (context, state) => OrganizationSetting(),
|
||||
),
|
||||
GoRoute(path: '/group', builder: (context, state) => GroupList()),
|
||||
GoRoute(
|
||||
path: '/getPerdiem',
|
||||
builder: (context, state) => ForexDataList(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/templateList',
|
||||
builder: (context, state) => TemplatesList(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/template',
|
||||
pageBuilder:
|
||||
(context, state) =>
|
||||
MaterialPage(child: Template.fromState(state)),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/templateForex',
|
||||
pageBuilder:
|
||||
(context, state) =>
|
||||
MaterialPage(child: TemplateForex.fromState(state)),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/approvallist',
|
||||
builder: (context, state) => ApprovalList(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/department',
|
||||
builder: (context, state) => DepartmentList(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/costcenter',
|
||||
builder: (context, state) => CostCenterList(),
|
||||
),
|
||||
GoRoute(path: '/hotels', builder: (context, state) => HotelsDataList()),
|
||||
GoRoute(
|
||||
path: '/statusdashboard',
|
||||
builder: (context, state) => StatusDashboard(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/traveller',
|
||||
builder: (context, state) => TravellerList(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/CreateGroup',
|
||||
pageBuilder:
|
||||
(context, state) => MaterialPage(child: Group.fromState(state)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// final GoRouter router = GoRouter(
|
||||
// routes: [
|
||||
// GoRoute(path: '/', builder: (context, state) => LoginPage()),
|
||||
// // GoRoute(
|
||||
// // path: '/authredirection',
|
||||
// // builder: (context, state) {
|
||||
// // final code = state.uri.queryParameters['code'];
|
||||
// // return MicrosoftPage(code: code);
|
||||
// // },
|
||||
// // ),
|
||||
// GoRoute(path: '/home', builder: (context, state) => HomePage()),
|
||||
// GoRoute(path: '/listAllPlan', builder: (context, state) => ListAllPlans()),
|
||||
// GoRoute(
|
||||
// path: '/listTravelAgentPlan',
|
||||
// builder: (context, state) => TravelAgentListPlans(),
|
||||
// ),
|
||||
// GoRoute(path: '/listPlan', builder: (context, state) => ListPlans()),
|
||||
// GoRoute(path: '/createPlan', builder: (context, state) => CreatePlan()),
|
||||
// GoRoute(path: '/allTrips/trips', builder: (context, state) => CreatePlan()),
|
||||
// GoRoute(path: '/approver/plans', builder: (context, state) => CreatePlan()),
|
||||
// GoRoute(path: '/listUser', builder: (context, state) => UserListScreen()),
|
||||
// GoRoute(
|
||||
// path: '/CreateUserDetails',
|
||||
// builder: (context, state) => CreateUserFormDetials(),
|
||||
// // builder: (context, state) {
|
||||
// // final userParam = state.uri.queryParameters['user'];
|
||||
// //
|
||||
// // final isEditProfile =
|
||||
// // state.uri.queryParameters['isEditProfile'] == 'true';
|
||||
// // final isViewMode = state.uri.queryParameters['isViewMode'] == 'true';
|
||||
// //
|
||||
// // final user = userParam != null
|
||||
// // ? jsonDecode(Uri.decodeComponent(userParam))
|
||||
// // : null;
|
||||
// //
|
||||
// // return CreateUserForm(
|
||||
// // apiselectedUser: user,
|
||||
// // isEditProfile: isEditProfile,
|
||||
// // isViewMode: isViewMode,
|
||||
// // );
|
||||
// // }
|
||||
// ),
|
||||
// GoRoute(
|
||||
// path: '/Policy',
|
||||
// // builder: (context, state) => Policy(),
|
||||
// pageBuilder:
|
||||
// (context, state) => MaterialPage(child: Policy.fromState(state)),
|
||||
// ),
|
||||
// GoRoute(path: '/PolicyList', builder: (context, state) => PolicyList()),
|
||||
// GoRoute(
|
||||
// path: '/OrganizationSetup',
|
||||
// builder: (context, state) => OrgSetUp(),
|
||||
// ),
|
||||
// GoRoute(
|
||||
// path: '/OrganizationSettings',
|
||||
// builder: (context, state) => OrganizationSetting(),
|
||||
// ),
|
||||
// GoRoute(path: '/group', builder: (context, state) => GroupList()),
|
||||
// GoRoute(path: '/getPerdiem', builder: (context, state) => ForexDataList()),
|
||||
// GoRoute(
|
||||
// path: '/templateList',
|
||||
// builder: (context, state) => TemplatesList(),
|
||||
// ),
|
||||
// // GoRoute(
|
||||
// // path: '/template',
|
||||
// // builder: (context, state) => MyHomePage(),
|
||||
// // ),
|
||||
// GoRoute(
|
||||
// path: '/template',
|
||||
// // builder: (context, state) => Template(),
|
||||
// pageBuilder:
|
||||
// (context, state) => MaterialPage(child: Template.fromState(state)),
|
||||
// ),
|
||||
// GoRoute(
|
||||
// path: '/templateForex',
|
||||
// pageBuilder:
|
||||
// (context, state) =>
|
||||
// MaterialPage(child: TemplateForex.fromState(state)),
|
||||
// ),
|
||||
// GoRoute(path: '/approvallist', builder: (context, state) => ApprovalList()),
|
||||
// GoRoute(path: '/department', builder: (context, state) => DepartmentList()),
|
||||
// GoRoute(path: '/costcenter', builder: (context, state) => CostCenterList()),
|
||||
// GoRoute(path: '/hotels', builder: (context, state) => HotelsDataList()),
|
||||
// GoRoute(
|
||||
// path: '/statusdashboard',
|
||||
// builder: (context, state) => StatusDashboard(),
|
||||
// ),
|
||||
// GoRoute(path: '/traveller', builder: (context, state) => TravellerList()),
|
||||
// GoRoute(
|
||||
// path: '/CreateGroup',
|
||||
// pageBuilder:
|
||||
// (context, state) => MaterialPage(child: Group.fromState(state)),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
|
||||
33
lib/routes/mainLayout.dart
Normal file
33
lib/routes/mainLayout.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
|
||||
import 'custom_appBar.dart';
|
||||
import 'custom_drawer.dart';
|
||||
|
||||
class MainLayout extends StatelessWidget {
|
||||
final Widget child;
|
||||
final bool isDesktop;
|
||||
|
||||
const MainLayout({required this.child, required this.isDesktop, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFf5f5f5),
|
||||
appBar: CustomAppBar(isDesktop: isDesktop),
|
||||
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
|
||||
body: Padding(
|
||||
padding:
|
||||
isDesktop
|
||||
? EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width * 0.1,
|
||||
vertical: 0,
|
||||
)
|
||||
: EdgeInsets.zero,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
|
||||
import '../services/apiService.dart';
|
||||
import 'custom_appBar.dart';
|
||||
import 'custom_drawer.dart';
|
||||
|
||||
@ -15,6 +16,8 @@ class OrganizationSetting extends StatefulWidget {
|
||||
}
|
||||
|
||||
class OrganizationSettingState extends State<OrganizationSetting> {
|
||||
final ApiService apiService = ApiService();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveBuilder(
|
||||
@ -84,7 +87,7 @@ class OrganizationSettingState extends State<OrganizationSetting> {
|
||||
'description': 'Create and Edit Perdiem Amount',
|
||||
},
|
||||
{
|
||||
'value': '/department',
|
||||
'value': '/forexTexmplate',
|
||||
'icon': Icons.group_add_outlined,
|
||||
'label': 'Forex Template',
|
||||
'description': 'Create and Edit Template',
|
||||
@ -244,9 +247,21 @@ class OrganizationSettingState extends State<OrganizationSetting> {
|
||||
child: Card(
|
||||
color: Colors.white,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
final route = item['value'] as String;
|
||||
context.go(route);
|
||||
onTap: () async {
|
||||
if (item['value'] as String ==
|
||||
"/forexTexmplate") {
|
||||
final data =
|
||||
await apiService.getForexTemplate();
|
||||
print("ForexId -- $data");
|
||||
|
||||
context.go(
|
||||
'/templateForex',
|
||||
extra: {'templateData': data},
|
||||
);
|
||||
} else {
|
||||
final route = item['value'] as String;
|
||||
context.go(route);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
|
||||
@ -5,11 +5,32 @@ import 'package:frontend/utils/auth_utils.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
import 'package:universal_html/js.dart';
|
||||
import '../../config/apiUrl.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class ApiService {
|
||||
Future<void> getOrganizationData() async {
|
||||
try {
|
||||
print("getUpdatedServices");
|
||||
final result = await fetchOrganization();
|
||||
print("UUPdatedServices - $result");
|
||||
|
||||
// ✅ Save to local storage
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = jsonEncode(result);
|
||||
await prefs.setString('org_data', jsonString);
|
||||
print("✅ Organization data saved to SharedPreferences.");
|
||||
|
||||
print("selectedOrg - $result");
|
||||
} catch (e) {
|
||||
print('Error fetching updatedServices list: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<dynamic>> fetchCountryList() async {
|
||||
final String apiUrldata = '$apiUrl/api/getcountryMaster';
|
||||
final token = await getToken();
|
||||
@ -33,7 +54,8 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
return data['data'];
|
||||
@ -68,7 +90,8 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
return data['data'];
|
||||
@ -136,7 +159,8 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
List<dynamic> plansJson = data['data']; // 'data' is a Map, not a List
|
||||
@ -190,7 +214,8 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
List<dynamic> plansJson = data['data']; // 'data' is a Map, not a List
|
||||
@ -239,7 +264,8 @@ class ApiService {
|
||||
print(data);
|
||||
if (!data.containsKey('data') || data['data'] is! Map) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a Map");
|
||||
"Invalid response format: 'data' field is missing or not a Map",
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> plansJson =
|
||||
@ -277,7 +303,8 @@ class ApiService {
|
||||
print(data);
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
return data['data'];
|
||||
} catch (e) {
|
||||
@ -313,7 +340,8 @@ class ApiService {
|
||||
print(data);
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
return data['data'];
|
||||
} catch (e) {
|
||||
@ -349,7 +377,8 @@ class ApiService {
|
||||
print(data);
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
return data['data'];
|
||||
} catch (e) {
|
||||
@ -385,7 +414,8 @@ class ApiService {
|
||||
print(data);
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
return data['data'];
|
||||
} catch (e) {
|
||||
@ -419,7 +449,8 @@ class ApiService {
|
||||
print(data);
|
||||
if (!data.containsKey('data') || data['data'] is! Map) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a Map");
|
||||
"Invalid response format: 'data' field is missing or not a Map",
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> plansJson =
|
||||
@ -460,7 +491,8 @@ class ApiService {
|
||||
print(data);
|
||||
if (!data.containsKey('data') || data['data'] is! Map) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a Map");
|
||||
"Invalid response format: 'data' field is missing or not a Map",
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure each item is a Map<String, dynamic>
|
||||
@ -527,33 +559,47 @@ class ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> viewPlan(BuildContext context, String planId,
|
||||
{bool isViewMode = false, bool isMyTrips = false}) async {
|
||||
static Future<void> viewPlan(
|
||||
BuildContext context,
|
||||
String planId, {
|
||||
bool isViewMode = false,
|
||||
bool isMyTrips = false,
|
||||
}) async {
|
||||
try {
|
||||
Map<String, dynamic> planData = await getViewPlanEdit(planId);
|
||||
print("ViewAAA - $planData");
|
||||
|
||||
context.go(isMyTrips ? '/createPlan' : '/allTrips/trips',
|
||||
extra: {'planData': planData, 'isViewMode': isViewMode});
|
||||
context.go(
|
||||
isMyTrips ? '/createPlan' : '/allTrips/trips',
|
||||
extra: {'planData': planData, 'isViewMode': isViewMode},
|
||||
);
|
||||
} catch (e) {
|
||||
print("Error fetching plan: $e");
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> viewPlanForApprover(BuildContext context, String planId,
|
||||
String? approverId, String? delegaterId,
|
||||
{bool isViewMode = false, bool isApprover = true}) async {
|
||||
static Future<void> viewPlanForApprover(
|
||||
BuildContext context,
|
||||
String planId,
|
||||
String? approverId,
|
||||
String? delegaterId, {
|
||||
bool isViewMode = false,
|
||||
bool isApprover = true,
|
||||
}) async {
|
||||
try {
|
||||
Map<String, dynamic> planData = await getViewPlanEdit(planId);
|
||||
print("ViewAAA - $planData");
|
||||
|
||||
context.replace('/approver/plans', extra: {
|
||||
'planData': planData,
|
||||
'approverId': approverId,
|
||||
'delegaterId': delegaterId,
|
||||
'isViewMode': isViewMode,
|
||||
'isApprover': isApprover,
|
||||
});
|
||||
context.replace(
|
||||
'/approver/plans',
|
||||
extra: {
|
||||
'planData': planData,
|
||||
'approverId': approverId,
|
||||
'delegaterId': delegaterId,
|
||||
'isViewMode': isViewMode,
|
||||
'isApprover': isApprover,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
print("Error fetching plan: $e");
|
||||
}
|
||||
@ -589,7 +635,8 @@ class ApiService {
|
||||
print(data);
|
||||
if (!data.containsKey('data') || data['data'] is! Map) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a Map");
|
||||
"Invalid response format: 'data' field is missing or not a Map",
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure each item is a Map<String, dynamic>
|
||||
@ -635,7 +682,8 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
return data['data'];
|
||||
@ -671,7 +719,8 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
return data['data'];
|
||||
@ -709,13 +758,14 @@ class ApiService {
|
||||
// Create a blob from the response body
|
||||
final blob = html.Blob([response.bodyBytes]);
|
||||
|
||||
// Generate a download URL for the blob
|
||||
// Generate a download URL for the blob
|
||||
final url = html.Url.createObjectUrlFromBlob(blob);
|
||||
|
||||
// Create a link element to trigger the download
|
||||
final anchor = html.AnchorElement(href: url)
|
||||
..setAttribute('download', 'trip_plan_$planId.pdf')
|
||||
..click();
|
||||
final anchor =
|
||||
html.AnchorElement(href: url)
|
||||
..setAttribute('download', 'trip_plan_$planId.pdf')
|
||||
..click();
|
||||
|
||||
// Revoke the download URL to free up resources
|
||||
html.Url.revokeObjectUrl(url);
|
||||
@ -772,13 +822,14 @@ class ApiService {
|
||||
// Create a blob from the response body
|
||||
final blob = html.Blob([response.bodyBytes]);
|
||||
|
||||
// Generate a download URL for the blob
|
||||
// Generate a download URL for the blob
|
||||
final url = html.Url.createObjectUrlFromBlob(blob);
|
||||
|
||||
// Create a link element to trigger the download
|
||||
final anchor = html.AnchorElement(href: url)
|
||||
..setAttribute('download', 'Forex_$forexId.pdf')
|
||||
..click();
|
||||
final anchor =
|
||||
html.AnchorElement(href: url)
|
||||
..setAttribute('download', 'Forex_$forexId.pdf')
|
||||
..click();
|
||||
|
||||
// Revoke the download URL to free up resources
|
||||
html.Url.revokeObjectUrl(url);
|
||||
@ -835,7 +886,8 @@ class ApiService {
|
||||
print(data);
|
||||
if (!data.containsKey('data') || data['data'] is! Map) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a Map");
|
||||
"Invalid response format: 'data' field is missing or not a Map",
|
||||
);
|
||||
}
|
||||
print('Single USer 1');
|
||||
|
||||
@ -880,7 +932,8 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
final List<dynamic> forexList = data['data'];
|
||||
@ -925,7 +978,8 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
final List<Map<String, dynamic>> listData =
|
||||
@ -984,7 +1038,62 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! Map) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a Map");
|
||||
"Invalid response format: 'data' field is missing or not a Map",
|
||||
);
|
||||
}
|
||||
|
||||
return Map<String, dynamic>.from(data['data']);
|
||||
} catch (e) {
|
||||
throw Exception('Error parsing response: $e');
|
||||
}
|
||||
} else {
|
||||
throw Exception('Failed to load department details');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getForexTemplate() async {
|
||||
final String apiUrldata =
|
||||
'$apiUrl/api/getForexTemplate?template_name=forex';
|
||||
|
||||
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('findout the result');
|
||||
// print(data.runtimeType);
|
||||
print(data);
|
||||
|
||||
// if (!data.containsKey('data') || data['data'] is! List) {
|
||||
// throw Exception(
|
||||
// "Invalid response format: 'data' field is missing or not a List");
|
||||
// }
|
||||
//
|
||||
// final List<Map<String, dynamic>> listData =
|
||||
// List<Map<String, dynamic>>.from(data['data']);
|
||||
//
|
||||
// if (listData.isEmpty) {
|
||||
// throw Exception("No department found with ID $id");
|
||||
// }
|
||||
//
|
||||
// return listData[0];
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! Map) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a Map",
|
||||
);
|
||||
}
|
||||
|
||||
return Map<String, dynamic>.from(data['data']);
|
||||
@ -997,7 +1106,9 @@ class ApiService {
|
||||
}
|
||||
|
||||
Future<bool> showCancelConfirmationDialog(
|
||||
BuildContext context, Color? layoutColor) async {
|
||||
BuildContext context,
|
||||
Color? layoutColor,
|
||||
) async {
|
||||
return await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
@ -1006,33 +1117,39 @@ class ApiService {
|
||||
title: Text(
|
||||
'Cancel Confirmation',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 18, fontWeight: FontWeight.w500),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
content: Text(
|
||||
'Do you want to cancel?',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14.5, fontWeight: FontWeight.w500),
|
||||
fontSize: 14.5,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: layoutColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(
|
||||
color: layoutColor ?? Colors.grey, width: 2),
|
||||
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: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: GoogleFonts.poppins(fontSize: 12),
|
||||
)),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: GoogleFonts.poppins(fontSize: 12),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: layoutColor, // Keep original color
|
||||
@ -1043,16 +1160,17 @@ class ApiService {
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(
|
||||
color: layoutColor ?? Colors.grey, width: 1),
|
||||
color: layoutColor ?? Colors.grey,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
onPressed: () => Navigator.of(context)
|
||||
.pop(true), // Disable when in view mode
|
||||
child: Text(
|
||||
"OK",
|
||||
style: GoogleFonts.poppins(fontSize: 12),
|
||||
),
|
||||
onPressed:
|
||||
() => Navigator.of(
|
||||
context,
|
||||
).pop(true), // Disable when in view mode
|
||||
child: Text("OK", style: GoogleFonts.poppins(fontSize: 12)),
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -1087,7 +1205,8 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
final List<Map<String, dynamic>> listData =
|
||||
@ -1132,7 +1251,8 @@ class ApiService {
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List");
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
final List<Map<String, dynamic>> listData =
|
||||
@ -1174,7 +1294,8 @@ class ApiService {
|
||||
print(data);
|
||||
if (!data.containsKey('data') || data['data'] is! Map) {
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a Map");
|
||||
"Invalid response format: 'data' field is missing or not a Map",
|
||||
);
|
||||
}
|
||||
print('Single USer 1');
|
||||
|
||||
@ -1194,7 +1315,6 @@ class ApiService {
|
||||
Future<Map<String, dynamic>> getTravellerDetailsFind(int id) async {
|
||||
final String apiUrldata = '$apiUrl/api/travellers/find?traveller_id=$id';
|
||||
|
||||
|
||||
//c
|
||||
final token = await getToken();
|
||||
|
||||
@ -1215,11 +1335,13 @@ class ApiService {
|
||||
final data = json.decode(response.body);
|
||||
|
||||
if (!data.containsKey('data') || data['data'] is! List) {
|
||||
throw Exception("Invalid response format: 'data' field is missing or not a List");
|
||||
throw Exception(
|
||||
"Invalid response format: 'data' field is missing or not a List",
|
||||
);
|
||||
}
|
||||
|
||||
final List<Map<String, dynamic>> listData =
|
||||
List<Map<String, dynamic>>.from(data['data']);
|
||||
List<Map<String, dynamic>>.from(data['data']);
|
||||
|
||||
if (listData.isEmpty) {
|
||||
throw Exception("No Traveller data found with ID $id");
|
||||
@ -1233,5 +1355,4 @@ class ApiService {
|
||||
throw Exception('Failed to load Hotel details');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user