4329 lines
159 KiB
Dart
4329 lines
159 KiB
Dart
import 'dart:convert';
|
||
import 'dart:async';
|
||
|
||
import 'dart:typed_data';
|
||
import 'package:dropdown_search/dropdown_search.dart';
|
||
import 'package:google_fonts/google_fonts.dart';
|
||
import 'package:super_tooltip/super_tooltip.dart';
|
||
import 'package:web/web.dart' as web;
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:frontend/Screens/plans/dynamic_itinerary_stepper.dart';
|
||
import 'package:frontend/utils/auth_utils.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'package:responsive_builder/responsive_builder.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import 'package:http/http.dart' as http;
|
||
import 'package:universal_html/html.dart' as html;
|
||
|
||
import '../../config/apiUrl.dart';
|
||
import '../../data/models/plan.dart';
|
||
import '../../routes/custom_appBar.dart';
|
||
import '../../routes/custom_drawer.dart';
|
||
import '../../services/apiService.dart';
|
||
import '../../widgets/custom_radio_button.dart';
|
||
import '../../widgets/custom_text_field.dart';
|
||
import '../../widgets/saving_loader.dart';
|
||
import '../approvals/approval_dialogs.dart';
|
||
import '../dialog/user_selection_dialog.dart';
|
||
import '../itnerary/flights.dart';
|
||
|
||
class CreatePlan extends StatefulWidget {
|
||
CreatePlan({super.key});
|
||
|
||
@override
|
||
_CreatePlansState createState() => _CreatePlansState();
|
||
}
|
||
|
||
class _CreatePlansState extends State<CreatePlan> {
|
||
final GlobalKey<CreateNewPlansState> _createPlanKey =
|
||
GlobalKey<CreateNewPlansState>();
|
||
|
||
Color layoutColor = Colors.redAccent;
|
||
Color bodyColor = Colors.white;
|
||
|
||
final FocusNode focusNode = FocusNode();
|
||
bool isFocused = false;
|
||
late FocusNode _tripTitleFocusNode;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_checkAuthAndLoadData();
|
||
// loadInitialData();
|
||
}
|
||
|
||
void _checkAuthAndLoadData() async {
|
||
final String? token = await getToken(); // Your async function to get token
|
||
|
||
if (token == null || token.isEmpty) {
|
||
// Token doesn't exist → redirect to login
|
||
context.go(
|
||
"/",
|
||
); // or use: router.go("/") if you're using `GoRouter` directly
|
||
return;
|
||
}
|
||
loadInitialData();
|
||
}
|
||
|
||
void loadInitialData() async {
|
||
String? layoutString = await getLayoutColor();
|
||
String? bodyStringColor = await getBodyColor();
|
||
|
||
setState(() {
|
||
layoutColor =
|
||
layoutString != null
|
||
? Color(int.parse(layoutString))
|
||
: Colors.redAccent;
|
||
|
||
bodyColor =
|
||
bodyStringColor != null
|
||
? Color(int.parse(bodyStringColor))
|
||
: Colors.white;
|
||
});
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return ResponsiveBuilder(
|
||
builder: (context, sizingInfo) {
|
||
bool isDesktop =
|
||
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||
|
||
return Scaffold(
|
||
// backgroundColor: Colors.white,
|
||
backgroundColor: Color(0xFFf5f5f5),
|
||
// backgroundColor: Color(0xFFFCFCFC),
|
||
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,
|
||
) {
|
||
final args = GoRouterState.of(context).extra as Map<String, dynamic>? ?? {};
|
||
// final planData = args?['planData'];
|
||
final bool isViewMode = args?['isViewMode'] ?? false;
|
||
final bool isApprover = args?['isApprover'] ?? false;
|
||
final String approverId = args['approverId'] ?? "";
|
||
final String delegaterId = args['delegaterId'] ?? "";
|
||
final String approverStatus = args['approver_status'] ?? "";
|
||
|
||
final Map<String, dynamic> planData =
|
||
args['planData'] as Map<String, dynamic>? ?? {};
|
||
|
||
// print("isViewMode: $isViewMode");
|
||
|
||
// final bool isViewMode = true;
|
||
// final planData = GoRouterState.of(context).extra as Map<String, dynamic>? ?? {};
|
||
|
||
print("RECived palndata");
|
||
print("RECived approverId - $approverId");
|
||
print("RECived delegateId - $delegaterId");
|
||
|
||
return Container(
|
||
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
|
||
padding: const EdgeInsets.all(8),
|
||
decoration: BoxDecoration(
|
||
// color: Colors.amber,
|
||
// color: bodyColor,
|
||
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
|
||
// border: Border.all(
|
||
// color: Colors.white,
|
||
// // color: Color(0xFFF7F7FB),
|
||
//
|
||
// width: 3.5)
|
||
),
|
||
child: Column(
|
||
children: [
|
||
// Container(
|
||
// color: Color(0xFFF4F4FB),
|
||
// padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
|
||
// child: Row(children: [
|
||
// Row(
|
||
// children: [
|
||
// Padding(
|
||
// padding: const EdgeInsets.all(8.0),
|
||
// child: Icon(
|
||
// Icons.create_new_folder_outlined,
|
||
// color: Color(0xFF84869A),
|
||
// size: 23,
|
||
// ),
|
||
// ),
|
||
// Text(
|
||
// isViewMode
|
||
// ? "View Plan"
|
||
// : (planData.isNotEmpty ? "Update Plan" : "New Plan"),
|
||
// style: TextStyle(fontSize: 18),
|
||
// ),
|
||
// ],
|
||
// ),
|
||
// Spacer(),
|
||
// ]),
|
||
// ),
|
||
Expanded(
|
||
child: Container(
|
||
// margin: isDesktop
|
||
// ? EdgeInsets.all(10.0)
|
||
// : EdgeInsets.only(
|
||
// left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
|
||
// padding: const EdgeInsets.all(10),
|
||
height:
|
||
isDesktop
|
||
? MediaQuery.of(context).size.height * 0.98
|
||
: MediaQuery.of(context).size.height,
|
||
// decoration: BoxDecoration(
|
||
// border: isDesktop
|
||
// ? Border.all(
|
||
// width: 2,
|
||
// color: Colors.white,
|
||
// // color: Color(0xFFF7F7FB),
|
||
// )
|
||
// : null,
|
||
// color: Colors.white,
|
||
// // color: Color(0xFFF7F7FB),
|
||
//
|
||
// // color: Colors.amber,
|
||
// ),
|
||
|
||
// color: bodyColor,
|
||
child: SingleChildScrollView(
|
||
child: Padding(
|
||
padding: EdgeInsets.all(10.0),
|
||
child: CreateNewPlan(
|
||
key: _createPlanKey,
|
||
bodyColor: bodyColor,
|
||
layoutColor: layoutColor,
|
||
isDesktop: isDesktop,
|
||
selectedPlanData: planData,
|
||
isViewMode: isViewMode,
|
||
isApprover: isApprover,
|
||
approverId: approverId,
|
||
delegaterId: delegaterId,
|
||
approverStatus: approverStatus,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
|
||
Container(
|
||
padding: const EdgeInsets.all(10),
|
||
color: Colors.white,
|
||
child:
|
||
isDesktop
|
||
? Row(
|
||
mainAxisAlignment: MainAxisAlignment.end,
|
||
// children: [Text("Button")],
|
||
children: _buildSubmit(
|
||
isDesktop,
|
||
isViewMode,
|
||
layoutColor,
|
||
isApprover,
|
||
),
|
||
)
|
||
: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: _buildSubmit(
|
||
isDesktop,
|
||
isViewMode,
|
||
layoutColor,
|
||
isApprover,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
List<Widget> _buildSubmit(
|
||
isDesktop,
|
||
bool isViewMode,
|
||
Color layoutColor,
|
||
bool isApprover,
|
||
) {
|
||
return [
|
||
ElevatedButton(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: Colors.white,
|
||
foregroundColor: layoutColor ?? Colors.blueAccent,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
side: BorderSide(color: layoutColor, width: 2),
|
||
),
|
||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||
),
|
||
onPressed: () {
|
||
final currentUri =
|
||
GoRouterState.of(
|
||
context,
|
||
).uri.toString(); // ✅ safer than `.location`
|
||
print("currentUri - $currentUri");
|
||
|
||
if (currentUri == "/allTrips/trips") {
|
||
context.go('/listAllPlan');
|
||
} else if (currentUri == "/createPlan") {
|
||
context.go('/listPlan');
|
||
} else if (currentUri == "/approver/plans") {
|
||
context.go('/approvallist');
|
||
} else if (currentUri == "/travelagent/trips") {
|
||
context.go('/listTravelAgentPlan');
|
||
} else {
|
||
isApprover ? context.go('/approvallist') : context.go('/listPlan');
|
||
}
|
||
},
|
||
child: Text("Cancel"),
|
||
),
|
||
SizedBox(width: 20),
|
||
MouseRegion(
|
||
cursor:
|
||
isViewMode
|
||
? SystemMouseCursors.forbidden
|
||
: SystemMouseCursors.click,
|
||
child: ElevatedButton(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor:
|
||
isViewMode ? layoutColor : layoutColor, // Keep original color
|
||
foregroundColor:
|
||
isViewMode ? Colors.white : Colors.white, // Keep original color
|
||
disabledBackgroundColor:
|
||
layoutColor, // Ensure color remains when disabled
|
||
disabledForegroundColor: Colors.white,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
side: BorderSide(color: layoutColor, width: 2),
|
||
),
|
||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||
),
|
||
onPressed:
|
||
isViewMode
|
||
? null
|
||
: () {
|
||
_createPlanKey.currentState?.handleSubmit();
|
||
}, // Disable when in view mode
|
||
|
||
child: Text("Submit"),
|
||
),
|
||
),
|
||
];
|
||
}
|
||
}
|
||
|
||
class CreateNewPlan extends StatefulWidget {
|
||
final bool isDesktop;
|
||
final bool isViewMode;
|
||
final bool isApprover;
|
||
final String? approverStatus;
|
||
final Color? bodyColor;
|
||
final Color? layoutColor;
|
||
|
||
final Map<String, dynamic> selectedPlanData;
|
||
|
||
final String approverId;
|
||
|
||
final String delegaterId;
|
||
const CreateNewPlan({
|
||
super.key,
|
||
required this.isDesktop,
|
||
required this.bodyColor,
|
||
required this.layoutColor,
|
||
required this.selectedPlanData,
|
||
required this.isViewMode,
|
||
required this.isApprover,
|
||
required this.approverStatus,
|
||
required this.approverId,
|
||
required this.delegaterId,
|
||
});
|
||
|
||
@override
|
||
CreateNewPlansState createState() => CreateNewPlansState();
|
||
}
|
||
|
||
class CreateNewPlansState extends State<CreateNewPlan> {
|
||
final GlobalKey<DynamicItineraryState> dynamicItineraryKey =
|
||
GlobalKey<DynamicItineraryState>();
|
||
final GlobalKey<FlightScreenState> flightScreenKey =
|
||
GlobalKey<FlightScreenState>();
|
||
|
||
late final ValueNotifier<String?> flightTripTypeNotifier;
|
||
|
||
final ApiService apiService = ApiService();
|
||
|
||
final TextEditingController _tripTitleController = TextEditingController();
|
||
final TextEditingController _descriptionController = TextEditingController();
|
||
final TextEditingController _soNumberController = TextEditingController();
|
||
final TextEditingController _excepntldescriptionController =
|
||
TextEditingController();
|
||
final TextEditingController _remarksController = TextEditingController();
|
||
|
||
// final FocusNode _tripTitleFocusNode = FocusNode();
|
||
// final FocusNode _descriptionFocusNode = FocusNode(); // Declare FocusNode
|
||
|
||
// bool _isdescriptionFocused = false;
|
||
// bool _isTripTitleFocused = false;
|
||
|
||
Color? layoutColor;
|
||
|
||
late String _selectedOption = "Option 1";
|
||
// late String? _selectedIsBillable = "Billable";
|
||
|
||
bool isStatusExpanded = false;
|
||
bool isShowApprovalAction = false;
|
||
String? selectedPlanId;
|
||
String? approverStatus;
|
||
|
||
String? userDetails;
|
||
String? userName;
|
||
String? selfId;
|
||
String? otherUserName;
|
||
String? selectedplanUserId;
|
||
bool? selectedIstravelUser;
|
||
late Color layoutColorForUser;
|
||
|
||
Map<String, dynamic>? apiData; // Store API response here
|
||
Map<String, dynamic>? apiDataForClass; // Store API response here
|
||
List<dynamic>? apiCountryData;
|
||
List<dynamic>? apiCostData; // Store API response here
|
||
bool isLoading = true; // Track loading state
|
||
String? TripPlanAction;
|
||
bool showDomestic = false;
|
||
bool showInternational = false;
|
||
bool hasAction = true;
|
||
bool hasSoNumber = false;
|
||
bool hasExceptionalClass = false;
|
||
bool hasExceptionalClassInUpdate = false;
|
||
|
||
late Map<String, String> costCenterMap;
|
||
List<String> costCenterIds = [];
|
||
// List<dynamic> apiCostData = []; // if you’re not already using this
|
||
|
||
late Map<String, String> purposeMap;
|
||
List<String> purposeKeys = [];
|
||
|
||
// Declare tooltip controller
|
||
late SuperTooltip tooltip;
|
||
|
||
String? orgId;
|
||
String? planUsrId;
|
||
String? planTravlrId;
|
||
String? statusValue;
|
||
|
||
String? _selectedTripType;
|
||
String? selectedCostCenterId;
|
||
String? _selectedIsBillable;
|
||
String? selectedFuncDept;
|
||
String? selectedPurpose;
|
||
|
||
Map<String, String?> validationErrors = {};
|
||
List<Map<String, dynamic>> miscellaneousList = [];
|
||
// List<Map<String, dynamic>> miscellaneousList = [{"special_request": 1, "comments": "posta", "indx": 1}];
|
||
List<Map<String, dynamic>> visaList = [];
|
||
List<Map<String, dynamic>> insuranceList = [];
|
||
List<Map<String, dynamic>> accommodationList = [];
|
||
List<Map<String, dynamic>> trainList = [];
|
||
List<Map<String, dynamic>> flightList = [];
|
||
List<Map<String, dynamic>> busList = [];
|
||
List<Map<String, dynamic>> taxiList = [];
|
||
List<Map<String, dynamic>> forexList = [];
|
||
|
||
List<Map<String, dynamic>> planStatusList = [];
|
||
|
||
Map<String, FocusNode> focusNodes = {};
|
||
Map<String, bool> focusStates = {};
|
||
|
||
late bool isApproverApproved = false;
|
||
late bool isApproverRejected = false;
|
||
|
||
String? temporaryMessage;
|
||
|
||
//Getter Method
|
||
Map<String, dynamic> get planData => {
|
||
"org_id": orgId,
|
||
"user_id": planUsrId,
|
||
"traveller_id": planTravlrId,
|
||
"trip_title": _tripTitleController.text,
|
||
"trip_type": _selectedTripType,
|
||
"cost_center_id": selectedCostCenterId,
|
||
"is_billable": _selectedIsBillable,
|
||
"purpose_of_travel": selectedPurpose,
|
||
"description": _descriptionController.text,
|
||
"exceptional_plan_reason": _excepntldescriptionController.text,
|
||
"functional_department": selectedFuncDept,
|
||
"so_number": _soNumberController.text,
|
||
"created_by": selfId,
|
||
"updated_by": selfId,
|
||
"is_active": "1",
|
||
"flight": flightList,
|
||
"accomodation": accommodationList,
|
||
"bus": busList,
|
||
"taxi": taxiList,
|
||
"train": trainList,
|
||
"visa": visaList,
|
||
"forex": forexList,
|
||
"insurance": insuranceList,
|
||
"miscellaneous": miscellaneousList,
|
||
// "status_value": statusValue,
|
||
};
|
||
|
||
List<String> dataHeader = [
|
||
"trip_planned",
|
||
"trip_type",
|
||
"cost_center_id",
|
||
"is_billable",
|
||
"purpose_of_travel",
|
||
"description",
|
||
"excepntldescription",
|
||
"functional_department",
|
||
"so_number",
|
||
];
|
||
|
||
void handleItineraryUpdate(String type, List<Map<String, dynamic>> newList) {
|
||
setState(() {
|
||
switch (type) {
|
||
case "Miscellaneous":
|
||
miscellaneousList = newList;
|
||
break;
|
||
case "Visa":
|
||
visaList = newList;
|
||
break;
|
||
case "Insurance":
|
||
insuranceList = newList;
|
||
break;
|
||
case "Train":
|
||
trainList = newList;
|
||
break;
|
||
case "Bus":
|
||
busList = newList;
|
||
break;
|
||
case "Taxi":
|
||
taxiList = newList;
|
||
break;
|
||
case "Forex":
|
||
forexList = newList;
|
||
break;
|
||
case "Flight":
|
||
flightList = newList;
|
||
break;
|
||
case "Accomodation":
|
||
accommodationList = newList;
|
||
break;
|
||
default:
|
||
print("Unknown itinerary type: $type");
|
||
}
|
||
});
|
||
print("Updated $type List: $newList");
|
||
}
|
||
|
||
void handleExcentionalClass(bool val) {
|
||
print("handleExcentionalClass - $val");
|
||
setState(() {
|
||
hasExceptionalClass = val;
|
||
});
|
||
}
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
print("approverStatus - ${widget.approverStatus}");
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
loadInitialData();
|
||
});
|
||
|
||
if (widget.approverStatus == "Approval pending") {
|
||
print("Status approver - Approval Pending");
|
||
}
|
||
flightTripTypeNotifier = ValueNotifier(null);
|
||
|
||
for (var field in dataHeader) {
|
||
focusNodes["${field}FocusNode"] = FocusNode();
|
||
focusStates["${field}Focused"] = false;
|
||
}
|
||
|
||
// trip_titleFocusNode.addListener(() {
|
||
// setState(() {}); // Rebuild when focus changes
|
||
// });
|
||
|
||
print("Focus Nodes KeysII: ${focusNodes.keys.toList()}");
|
||
print("Focus States KeysII: ${focusStates.keys.toList()}");
|
||
|
||
for (var key in focusNodes.keys) {
|
||
_addFocusListener(focusNodes[key]!, (focus) {
|
||
setState(() {
|
||
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
|
||
});
|
||
});
|
||
}
|
||
|
||
fetchUserDetails();
|
||
|
||
fetchPlans();
|
||
fetchCostCenter();
|
||
fetchCountryList();
|
||
|
||
print("hasExceptionalClass - $hasExceptionalClass");
|
||
print("hasExceptionalClassInUpdate - $hasExceptionalClassInUpdate");
|
||
|
||
// _tripTitleFocusNode.addListener(() {
|
||
// setState(() {
|
||
// _isTripTitleFocused = _tripTitleFocusNode.hasFocus;
|
||
// });
|
||
// });
|
||
//
|
||
// _descriptionFocusNode.addListener(() {
|
||
// setState(() {
|
||
// _isdescriptionFocused = _descriptionFocusNode.hasFocus;
|
||
// });
|
||
// });
|
||
|
||
handleUpdateData();
|
||
}
|
||
|
||
void loadInitialData() async {
|
||
String? layoutString = await getLayoutColor();
|
||
|
||
setState(() {
|
||
layoutColor =
|
||
layoutString != null
|
||
? Color(int.parse(layoutString))
|
||
: Colors.redAccent;
|
||
});
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
for (var node in focusNodes.values) {
|
||
node.dispose();
|
||
}
|
||
|
||
// _tripTitleFocusNode.dispose();
|
||
// _descriptionFocusNode.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
||
node.addListener(() {
|
||
setState(() {
|
||
updateState(node.hasFocus);
|
||
});
|
||
});
|
||
}
|
||
|
||
void handleSelectedUser() {
|
||
if (widget.selectedPlanData != null) {
|
||
setState(() {
|
||
String userId = widget.selectedPlanData['user_id'] ?? '';
|
||
String userName = widget.selectedPlanData['user_name'] ?? '';
|
||
String travellerId = widget.selectedPlanData['traveller_id'] ?? '';
|
||
String travellerName = widget.selectedPlanData['traveller_name'] ?? '';
|
||
|
||
print("TestplanUsrId $userId --- $travellerId");
|
||
|
||
if (travellerId.isNotEmpty && travellerId != "0") {
|
||
print("TestplanUsrId1: $travellerId");
|
||
planTravlrId = travellerId;
|
||
_selectedOption = "Option 3";
|
||
otherUserName = travellerName;
|
||
} else if (userId.isNotEmpty && userId != "0") {
|
||
print("TestplanUsrId : $userId");
|
||
print("TestplanUsrIdSelf : $selfId");
|
||
planUsrId = userId;
|
||
print("TestplanUsrId2 : $planUsrId");
|
||
_selectedOption = (selfId != userId) ? "Option 2" : "Option 1";
|
||
otherUserName = userName;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
void handleUpdateData() {
|
||
if (widget.selectedPlanData != null) {
|
||
setState(() {
|
||
statusValue = widget.selectedPlanData['status_value'] ?? '';
|
||
|
||
print("STATUS____ : $statusValue");
|
||
|
||
planUsrId = widget.selectedPlanData['user_id'] ?? '';
|
||
_tripTitleController.text = widget.selectedPlanData['trip_title'] ?? '';
|
||
_descriptionController.text =
|
||
widget.selectedPlanData['description'] ?? '';
|
||
|
||
_soNumberController.text = widget.selectedPlanData['so_number'] ?? '';
|
||
|
||
if (_soNumberController.text != "") {
|
||
hasSoNumber = true;
|
||
}
|
||
|
||
_excepntldescriptionController.text =
|
||
widget.selectedPlanData['exceptional_plan_reason'] ?? '';
|
||
|
||
if (_excepntldescriptionController.text != "" &&
|
||
_excepntldescriptionController.text != null) {
|
||
hasExceptionalClassInUpdate = true;
|
||
print("hasExceptionalClass1 - $hasExceptionalClass");
|
||
print("hasExceptionalClassInUpdate1 - $hasExceptionalClassInUpdate");
|
||
}
|
||
|
||
_selectedTripType = widget.selectedPlanData['trip_type'];
|
||
flightTripTypeNotifier.value = widget.selectedPlanData['trip_type'];
|
||
_selectedIsBillable =
|
||
widget.selectedPlanData['is_billable'] == "1" ? "1" : "2";
|
||
|
||
// selectedCostCenterId = widget.selectedPlanData['cost_center_id']?.toString() ;
|
||
// selectedPurpose = widget.selectedPlanData['purpose_of_travel']?.toString();
|
||
// selectedFuncDept =widget.selectedPlanData['functional_department']?.toString();
|
||
|
||
if (widget.selectedPlanData!["cost_center_id"] != null) {
|
||
selectedCostCenterId =
|
||
widget.selectedPlanData!["cost_center_id"].toString();
|
||
}
|
||
|
||
if (widget.selectedPlanData['plan_status'] != null) {
|
||
planStatusList = List<Map<String, dynamic>>.from(
|
||
widget.selectedPlanData['plan_status'] ?? [],
|
||
);
|
||
}
|
||
|
||
//
|
||
if (widget.selectedPlanData!["purpose_of_travel"] != null) {
|
||
selectedPurpose =
|
||
widget.selectedPlanData!["purpose_of_travel"].toString();
|
||
}
|
||
|
||
if (widget.selectedPlanData!["functional_department"] != null) {
|
||
// selectedFuncDept = widget.selectedPlanData!["functional_department"].toString();
|
||
selectedFuncDept = widget.selectedPlanData!["functional_department"];
|
||
}
|
||
|
||
// Assign lists from selectedPlanData, ensuring they are properly formatted
|
||
flightList = List<Map<String, dynamic>>.from(
|
||
widget.selectedPlanData['flight'] ?? [],
|
||
);
|
||
accommodationList = List<Map<String, dynamic>>.from(
|
||
widget.selectedPlanData['accomodation'] ?? [],
|
||
);
|
||
busList = List<Map<String, dynamic>>.from(
|
||
widget.selectedPlanData['bus'] ?? [],
|
||
);
|
||
taxiList = List<Map<String, dynamic>>.from(
|
||
widget.selectedPlanData['taxi'] ?? [],
|
||
);
|
||
trainList = List<Map<String, dynamic>>.from(
|
||
widget.selectedPlanData['train'] ?? [],
|
||
);
|
||
visaList = List<Map<String, dynamic>>.from(
|
||
widget.selectedPlanData['visa'] ?? [],
|
||
);
|
||
forexList = List<Map<String, dynamic>>.from(
|
||
widget.selectedPlanData['forex'] ?? [],
|
||
);
|
||
insuranceList = List<Map<String, dynamic>>.from(
|
||
widget.selectedPlanData['insurance'] ?? [],
|
||
);
|
||
miscellaneousList = List<Map<String, dynamic>>.from(
|
||
widget.selectedPlanData['miscellaneous'] ?? [],
|
||
);
|
||
});
|
||
|
||
if (widget.selectedPlanData['trip_type'] != null) {
|
||
int? tripId = int.tryParse(
|
||
widget.selectedPlanData['trip_type'].toString(),
|
||
);
|
||
fetchTrainFlightClass(tripId!);
|
||
}
|
||
|
||
if (widget.selectedPlanData.containsKey('plan_id') &&
|
||
widget.selectedPlanData['plan_id'] != null) {
|
||
print("Plan ID exists: ${widget.selectedPlanData['plan_id']}");
|
||
selectedPlanId = widget.selectedPlanData['plan_id']?.toString();
|
||
} else {
|
||
print("Plan ID is missing or null");
|
||
}
|
||
|
||
print("updatedPlanDAta - $planData");
|
||
}
|
||
}
|
||
|
||
void setTripPlanAction() {
|
||
setState(() {
|
||
if (TripPlanAction == "Plan Creation Not Allowed") {
|
||
showDomestic = false;
|
||
showInternational = false;
|
||
hasAction = false;
|
||
} else if (TripPlanAction == "Only Domestic Plan Creation Allowed") {
|
||
showDomestic = true;
|
||
showInternational = false;
|
||
hasAction = true;
|
||
} else if (TripPlanAction == "Only International Plan Creation Allowed") {
|
||
showDomestic = false;
|
||
showInternational = true;
|
||
hasAction = true;
|
||
} else if (TripPlanAction == "Both Type Plan Creation Allowed") {
|
||
showDomestic = true;
|
||
showInternational = true;
|
||
hasAction = true;
|
||
}
|
||
});
|
||
}
|
||
|
||
Future<void> getPdfDownload() async {
|
||
final String apiUrldata =
|
||
'$apiUrl/api/plans/download?plan_id=$selectedPlanId';
|
||
|
||
// final String apiUrldata = '$apiUrl/auth/googlelogin';
|
||
|
||
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 {
|
||
print("PDf Dowloaded");
|
||
|
||
// Create a blob from the response body
|
||
final blob = html.Blob([response.bodyBytes]);
|
||
|
||
// 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_$selectedPlanId.pdf')
|
||
..click();
|
||
|
||
// Revoke the download URL to free up resources
|
||
html.Url.revokeObjectUrl(url);
|
||
} catch (e) {
|
||
throw Exception('Error parsing response: $e');
|
||
}
|
||
} else if (response.statusCode == 404) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (BuildContext context) {
|
||
return AlertDialog(
|
||
title: Text('File not found.'),
|
||
// content: Text('File not found.'),
|
||
actions: [
|
||
TextButton(
|
||
child: Text('OK'),
|
||
onPressed: () {
|
||
Navigator.of(context).pop(); // Close the dialog
|
||
},
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
} else {
|
||
throw Exception('Failed to load plans');
|
||
}
|
||
}
|
||
|
||
Future<void> getSelectedPlanFor() async {
|
||
var userTripId;
|
||
// if (!mounted) return;
|
||
print("getSelectedPlanFor");
|
||
setState(() {
|
||
if (selectedplanUserId != null) {
|
||
print("Is Not USER ID - $planUsrId ");
|
||
if (selectedIstravelUser!) {
|
||
planUsrId = "";
|
||
planTravlrId = selectedplanUserId;
|
||
userTripId = selectedplanUserId;
|
||
} else {
|
||
planUsrId = selectedplanUserId;
|
||
planTravlrId = "";
|
||
userTripId = selectedplanUserId;
|
||
}
|
||
} else {
|
||
print("Is USER ID - $planUsrId ");
|
||
planUsrId = selfId;
|
||
planTravlrId = "";
|
||
_selectedOption = "Option 1";
|
||
userTripId = selfId;
|
||
}
|
||
});
|
||
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString('trip_planned_user', userTripId);
|
||
|
||
print("USER ID - $planUsrId , TRAVELLER ID - $planTravlrId");
|
||
}
|
||
|
||
void fetchUserDetails() async {
|
||
final details = await getUserDetails();
|
||
TripPlanAction = await getTripPlanAction();
|
||
print("TripPlanAction- $TripPlanAction");
|
||
print("details- $details");
|
||
|
||
if (details != null) {
|
||
setState(() {
|
||
userDetails = details.toString(); // Store the full Map
|
||
userName = details['name']; // Extract the name
|
||
selfId = details['user_id'];
|
||
});
|
||
}
|
||
orgId = await getOrgId();
|
||
print("userDetails - $selfId");
|
||
// handleSelectedUser();
|
||
getSelectedPlanFor();
|
||
setTripPlanAction();
|
||
|
||
handleSelectedUser();
|
||
// handleUpdateData();
|
||
}
|
||
|
||
Future<String?> getToken() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
return prefs.getString('auth_token');
|
||
}
|
||
|
||
Future<String?> getUserId() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
return prefs.getString('userId');
|
||
}
|
||
|
||
Future<Map<String, String>?> getUserDetails() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
final userData = prefs.getString('user_data');
|
||
|
||
if (userData != null) {
|
||
final decodedData = jsonDecode(userData);
|
||
|
||
return {
|
||
'user_id': decodedData['user_id'].toString(),
|
||
'name': "${decodedData['first_name']} ${decodedData['last_name']}",
|
||
};
|
||
}
|
||
return null;
|
||
}
|
||
|
||
Future<void> fetchPlans() async {
|
||
final String apiUrldata = '$apiUrl/api/getDropdownMaster';
|
||
|
||
final token = await getToken();
|
||
|
||
if (token == null) {
|
||
throw Exception('Token not found. Please log in.');
|
||
}
|
||
|
||
final response = await http.get(
|
||
Uri.parse(apiUrldata),
|
||
headers: {
|
||
'Authorization': 'Bearer $token',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
try {
|
||
final data = json.decode(response.body);
|
||
print(data);
|
||
|
||
if (!data.containsKey('data') || data['data'] is! Map) {
|
||
throw Exception(
|
||
"Invalid response format: 'data' field is missing or not a Map",
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> plansJson =
|
||
data['data']; // 'data' is a Map, not a List
|
||
setState(() {
|
||
apiData = plansJson; // Store API response in state
|
||
isLoading = false;
|
||
});
|
||
} catch (e) {
|
||
throw Exception('Error parsing response: $e');
|
||
}
|
||
} else {
|
||
throw Exception('Failed to load plans');
|
||
}
|
||
}
|
||
|
||
Future<void> fetchCostCenter() async {
|
||
final String apiUrldata = '$apiUrl/api/getCostCenterMaster';
|
||
|
||
final token = await getToken();
|
||
|
||
final userId = await getUserId();
|
||
|
||
// print("SUSRTRT- $userId");
|
||
//
|
||
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("CostCenterdropdoen - $data");
|
||
|
||
if (!data.containsKey('data') || data['data'] is! List) {
|
||
throw Exception(
|
||
"Invalid response format: 'data' field is missing or not a List",
|
||
);
|
||
}
|
||
|
||
List<dynamic> plansJson = data['data']; // 'data' is a Map, not a List
|
||
// setState(() {
|
||
// apiCostData = plansJson; // Store API response in state
|
||
// // if(apiCostData!.isNotEmpty){
|
||
// // selectedCostCenterId =apiCostData?.first['department_id'];
|
||
// // }
|
||
//
|
||
// if (apiCostData != null && apiCostData!.isNotEmpty) {
|
||
// selectedCostCenterId ??= apiCostData!.first['name']?.toString();
|
||
// }
|
||
// });
|
||
|
||
setState(() {
|
||
apiCostData = plansJson;
|
||
|
||
print("apiCostData...1");
|
||
costCenterMap = {
|
||
for (var item in apiCostData!)
|
||
// item['department_id'].toString(): item['name'].toString(),
|
||
item['cost_center_id'].toString(): item['name'].toString(),
|
||
};
|
||
|
||
print("apiCostData...122");
|
||
costCenterIds = costCenterMap.keys.toList();
|
||
|
||
// Optionally auto-select the first item if not already selected
|
||
selectedCostCenterId ??=
|
||
costCenterIds.isNotEmpty ? costCenterIds.first : null;
|
||
});
|
||
|
||
print('plansJSON');
|
||
} catch (e) {
|
||
throw Exception('Error parsing response: $e');
|
||
}
|
||
} else {
|
||
throw Exception('Failed to load plans');
|
||
}
|
||
}
|
||
|
||
Future<void> fetchCountryList() async {
|
||
final String apiUrldata = '$apiUrl/api/getcountryMaster';
|
||
|
||
final token = await getToken();
|
||
|
||
final userId = await getUserId();
|
||
|
||
// print("SUSRTRT- $userId");
|
||
//
|
||
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("Country - $data");
|
||
|
||
if (!data.containsKey('data') || data['data'] is! List) {
|
||
throw Exception(
|
||
"Invalid response format: 'data' field is missing or not a List",
|
||
);
|
||
}
|
||
|
||
List<dynamic> plansJson = data['data']; // 'data' is a Map, not a List
|
||
|
||
if (data['data'] is List) {
|
||
List<dynamic> plansJson = data['data'];
|
||
print("plansJson.length - ${plansJson.length}");
|
||
} else {
|
||
print("The 'data' key does not contain a list.");
|
||
}
|
||
|
||
setState(() {
|
||
apiCountryData = plansJson; // Store API response in state
|
||
});
|
||
print('plansJSONContry - $plansJson');
|
||
} catch (e) {
|
||
throw Exception('Error parsing response: $e');
|
||
}
|
||
} else {
|
||
throw Exception('Failed to load plans');
|
||
}
|
||
}
|
||
|
||
Future<void> fetchTrainFlightClass(int tripId) async {
|
||
final userId =
|
||
(planUsrId?.toString().isNotEmpty == true)
|
||
? planUsrId.toString()
|
||
: (planTravlrId?.toString().isNotEmpty == true)
|
||
? planTravlrId.toString()
|
||
: '';
|
||
|
||
// final String apiUrldata = '$apiUrl/api/getDropdownMaster';
|
||
final String apiUrldata =
|
||
'$apiUrl/api/getFlightAndTrainClass?user_id=$userId&trip_type=$tripId';
|
||
|
||
final token = await getToken();
|
||
|
||
if (token == null) {
|
||
throw Exception('Token not found. Please log in.');
|
||
}
|
||
|
||
final response = await http.get(
|
||
Uri.parse(apiUrldata),
|
||
headers: {
|
||
'Authorization': 'Bearer $token',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
try {
|
||
final data = json.decode(response.body);
|
||
print(data);
|
||
|
||
if (!data.containsKey('data') || data['data'] is! Map) {
|
||
throw Exception(
|
||
"Invalid response format: 'data' field is missing or not a Map",
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> plansJson =
|
||
data['data']; // 'data' is a Map, not a List
|
||
setState(() {
|
||
apiDataForClass = plansJson; // Store API response in state
|
||
isLoading = false;
|
||
});
|
||
} catch (e) {
|
||
throw Exception('Error parsing response: $e');
|
||
}
|
||
} else {
|
||
throw Exception('Failed to load plans');
|
||
}
|
||
}
|
||
|
||
// Handle Submit
|
||
|
||
bool validateForm() {
|
||
print("validateForm");
|
||
validationErrors.clear(); // Clear previous errors
|
||
print("validateForm.....1");
|
||
// Ensure either "user_id" or "traveller_id" is provided
|
||
if ((planUsrId == null || planUsrId!.isEmpty) &&
|
||
(planTravlrId == null || planTravlrId!.isEmpty)) {
|
||
validationErrors["user_id"] =
|
||
"Either User ID or Traveller ID is required";
|
||
validationErrors["traveller_id"] =
|
||
"Either User ID or Traveller ID is required";
|
||
}
|
||
print("validateForm.....2");
|
||
final requiredFields = {
|
||
if (TripPlanAction != "Plan Creation Not Allowed") //
|
||
"trip_type": _selectedTripType,
|
||
"trip_title": _tripTitleController.text,
|
||
"cost_center_id": selectedCostCenterId,
|
||
"functional_department": selectedFuncDept,
|
||
"purpose_of_travel": selectedPurpose,
|
||
|
||
if (hasExceptionalClass)
|
||
"exceptional_plan_reason": _excepntldescriptionController.text,
|
||
if (hasSoNumber) "so_number": _soNumberController.text,
|
||
};
|
||
print("validateForm.....3");
|
||
for (var entry in requiredFields.entries) {
|
||
if (entry.value == null || entry.value!.isEmpty) {
|
||
validationErrors[entry.key] = "Required";
|
||
// "${entry.key.replaceAll('_', ' ').toUpperCase()} Required";
|
||
}
|
||
}
|
||
print("validateForm.....1");
|
||
// Validate at least one service is selected
|
||
final serviceLists = [
|
||
flightList,
|
||
accommodationList,
|
||
busList,
|
||
taxiList,
|
||
trainList,
|
||
visaList,
|
||
forexList,
|
||
insuranceList,
|
||
miscellaneousList,
|
||
];
|
||
|
||
// bool anyServiceSelected = serviceLists.any(
|
||
// (list) => list != null && list.isNotEmpty,
|
||
// );
|
||
bool anyServiceSelected = serviceLists.any((list) {
|
||
return list != null &&
|
||
list.any((entry) => entry['is_active'].toString() == "1");
|
||
});
|
||
|
||
if (!anyServiceSelected) {
|
||
validationErrors["services"] = "Please select at least one service";
|
||
|
||
setState(() {
|
||
temporaryMessage = "Please select at least one service";
|
||
});
|
||
|
||
// Clear message after 3 seconds
|
||
Future.delayed(Duration(seconds: 3), () {
|
||
if (mounted) {
|
||
setState(() {
|
||
temporaryMessage = null;
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
return validationErrors.isEmpty; // Returns true if no errors
|
||
}
|
||
|
||
Future<void> callApproveAPI(String planId, String userId) async {
|
||
await postToAPI(
|
||
endpoint: '/api/plans/approvePlan',
|
||
data: {
|
||
"plan_id": planId,
|
||
"user_id": widget.approverId,
|
||
"delegation_user_id": widget.delegaterId,
|
||
},
|
||
methodName: 'Plan Approval',
|
||
);
|
||
}
|
||
|
||
Future<void> callRejectAPI(
|
||
String planId,
|
||
String userId,
|
||
String remarks,
|
||
) async {
|
||
await postToAPI(
|
||
endpoint: '/api/plans/rejectPlan',
|
||
data: {
|
||
"plan_id": planId,
|
||
"user_id": widget.approverId,
|
||
"delegation_user_id": widget.delegaterId,
|
||
"reason": remarks,
|
||
},
|
||
methodName: 'Plan Rejection',
|
||
);
|
||
}
|
||
|
||
Future<void> postToAPI({
|
||
required String endpoint,
|
||
required Map<String, dynamic> data,
|
||
String methodName = '',
|
||
}) async {
|
||
final token = await getToken();
|
||
|
||
if (token == null) {
|
||
throw Exception('Token not found. Please log in.');
|
||
}
|
||
|
||
try {
|
||
final response = await http.post(
|
||
Uri.parse('$apiUrl$endpoint'),
|
||
headers: {
|
||
'Authorization': 'Bearer $token',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: jsonEncode(data),
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
print("$methodName successful!");
|
||
print("Response: ${response.body}");
|
||
|
||
// await apiService.getViewPlan(
|
||
// data['planId'],planData
|
||
// );
|
||
print("ViewAAA - $planData");
|
||
print("Call viewPlanForApprover");
|
||
|
||
// ApiService.viewPlanForApprover(
|
||
// context,
|
||
// data['plan_id'],
|
||
// data['approverId'],
|
||
// data['delegaterId'],
|
||
// data['approver_status'],
|
||
//
|
||
// isViewMode: false,
|
||
// isApprover: true,
|
||
// );
|
||
// Close loading dialog (ONLY if still mounted)
|
||
if (mounted) Navigator.of(context, rootNavigator: true).pop();
|
||
context.go('/approvallist');
|
||
} else {
|
||
print("$methodName failed. Status: ${response.statusCode}");
|
||
print("Error: ${response.body}");
|
||
}
|
||
} catch (e) {
|
||
print("Error in $methodName: $e");
|
||
}
|
||
}
|
||
|
||
Future<void> checkExceptionalClass() async {
|
||
// pretend we fetch something
|
||
await Future.delayed(Duration(seconds: 1)); // Example async operation
|
||
|
||
int exceptionalCount = 0;
|
||
|
||
bool hasExceptional(List<Map<String, dynamic>> list) {
|
||
return list.any(
|
||
(item) =>
|
||
item['is_this_exceptional']?.toString() == '1' &&
|
||
item['is_active']?.toString() == '1',
|
||
);
|
||
}
|
||
|
||
bool hasExceptionalInFlights(List<Map<String, dynamic>> flights) {
|
||
for (var flight in flights) {
|
||
if (flight['is_active']?.toString() == '1') {
|
||
final trips = flight['trips'];
|
||
if (trips is List) {
|
||
final exceptionalTrip = trips.any(
|
||
(trip) => trip['is_this_exceptional']?.toString() == '1',
|
||
);
|
||
if (exceptionalTrip) return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
if (hasExceptionalInFlights(flightList)) exceptionalCount++;
|
||
if (hasExceptional(trainList)) exceptionalCount++;
|
||
if (hasExceptional(accommodationList)) exceptionalCount++;
|
||
|
||
setState(() {
|
||
hasExceptionalClass = exceptionalCount >= 1;
|
||
if (hasExceptionalClass &&
|
||
_excepntldescriptionController.text.trim().isEmpty) {
|
||
_showExceptionalReasonModal(context);
|
||
}
|
||
if (!hasExceptionalClass) {
|
||
_excepntldescriptionController.text = "";
|
||
}
|
||
});
|
||
|
||
print("hasExceptionalClass: $hasExceptionalClass");
|
||
}
|
||
|
||
void handleSubmit() async {
|
||
await checkExceptionalClass(); // Do async work first
|
||
setState(() {
|
||
// await checkExceptionalClass();
|
||
|
||
print("Handle Submit....after check");
|
||
|
||
print("Hansle Submit....11");
|
||
if (validateForm() && temporaryMessage == null) {
|
||
print("Hansle Submit....11222");
|
||
// if (selectedPlanId != null && selectedPlanId!.isNotEmpty) {
|
||
// planData['plan_id'] = selectedPlanId; // Add plan_id for update
|
||
// }
|
||
//
|
||
// print("Form submitted successfully:"
|
||
// " ${_remarksController.text}, ${planData['user_id']}, ${planData['traveller_id']}, "
|
||
// "${planData['traveller_id']},"
|
||
// " ${selectedPlanId}, "
|
||
// " ");
|
||
print("Start loader at: ${DateTime.now()}");
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (context) => const SavingLoader(),
|
||
);
|
||
|
||
postPlanData(planData);
|
||
// await postPlanData(planData);
|
||
print("Hide loader at: ${DateTime.now()}");
|
||
// Close loading dialog (ONLY if still mounted)
|
||
Future.delayed(Duration(seconds: 12), () {
|
||
if (mounted) {
|
||
Navigator.of(context, rootNavigator: true).pop();
|
||
|
||
final currentUri =
|
||
GoRouterState.of(
|
||
context,
|
||
).uri.toString(); // ✅ safer than `.location`
|
||
print("currentUri - $currentUri");
|
||
|
||
if (currentUri == "/allTrips/trips") {
|
||
context.go('/listAllPlan');
|
||
} else if (currentUri == "/createPlan") {
|
||
context.go('/listPlan');
|
||
} else {
|
||
widget.isApprover
|
||
? context.go('/approvallist')
|
||
: context.go('/listPlan');
|
||
}
|
||
}
|
||
});
|
||
|
||
// if (mounted) Navigator.of(context, rootNavigator: true).pop();
|
||
}
|
||
});
|
||
}
|
||
|
||
Future<void> postPlanData(planData) async {
|
||
final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan';
|
||
|
||
final token = await getToken(); // Fetch token
|
||
|
||
if (token == null) {
|
||
throw Exception('Token not found. Please log in.');
|
||
}
|
||
|
||
if (selectedPlanId != null && selectedPlanId!.isNotEmpty) {
|
||
planData['plan_id'] = selectedPlanId; // Add plan_id for update
|
||
}
|
||
|
||
print("POSTPlanTesting------- $planData}");
|
||
|
||
try {
|
||
final response = await http.post(
|
||
Uri.parse(apiUrldata),
|
||
headers: {
|
||
'Authorization': 'Bearer $token',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: jsonEncode(planData), // Convert map to JSON
|
||
);
|
||
|
||
if (response.statusCode == 200) {
|
||
print("Plan submitted successfully!");
|
||
print("Response: ${response.body}");
|
||
//
|
||
// final currentUri =
|
||
// GoRouterState.of(
|
||
// context,
|
||
// ).uri.toString(); // ✅ safer than `.location`
|
||
// print("currentUri - $currentUri");
|
||
//
|
||
// if (currentUri == "/allTrips/trips") {
|
||
// context.go('/listAllPlan');
|
||
// } else if (currentUri == "/createPlan") {
|
||
// context.go('/listPlan');
|
||
// } else {
|
||
// widget.isApprover
|
||
// ? context.go('/approvallist')
|
||
// : context.go('/listPlan');
|
||
// }
|
||
} else {
|
||
print("Failed to submit plan. Status: ${response.statusCode}");
|
||
print("Error: ${response.body}");
|
||
|
||
showDialog(
|
||
context: context,
|
||
builder: (BuildContext context) {
|
||
return AlertDialog(
|
||
title: Text("Trip Creation Failed"),
|
||
content: Text(
|
||
"There was a problem submitting your plan. Please try again.",
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
child: Text("OK"),
|
||
onPressed: () {
|
||
Navigator.of(context).pop();
|
||
},
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|
||
} catch (e) {
|
||
print(" Error submitting plan: $e");
|
||
}
|
||
}
|
||
|
||
Widget build(BuildContext context) {
|
||
bool hasApprovals = planStatusList.any(
|
||
(item) => item.entries.any(
|
||
(entry) =>
|
||
entry.key.contains('status') &&
|
||
entry.value != null &&
|
||
entry.value.toString().isNotEmpty,
|
||
),
|
||
);
|
||
|
||
return FocusTraversalGroup(
|
||
policy: OrderedTraversalPolicy(), // 👈 more predictable tab order
|
||
descendantsAreFocusable: true,
|
||
child: ResponsiveBuilder(
|
||
builder: (context, sizingInfo) {
|
||
bool isMobile = sizingInfo.isMobile;
|
||
bool isDesktop =
|
||
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
ResponsiveBuilder(
|
||
builder: (context, sizingInfo) {
|
||
bool isDesktop =
|
||
sizingInfo.deviceScreenType ==
|
||
DeviceScreenType.desktop;
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.all(5),
|
||
child:
|
||
isDesktop
|
||
? Row(
|
||
crossAxisAlignment:
|
||
CrossAxisAlignment.start,
|
||
// mainAxisAlignment: MainAxisAlignment.end,
|
||
children: [
|
||
_buildTripName(isDesktop),
|
||
if (isDesktop) Spacer(),
|
||
..._buildApproverControls(isDesktop),
|
||
|
||
// Text(
|
||
// widget.isViewMode
|
||
// ? "View Plan"
|
||
// : (selectedPlanId != null &&
|
||
// selectedPlanId!.isNotEmpty
|
||
// ? "Update Plan"
|
||
// : "New Plan"),
|
||
// style: TextStyle(fontSize: 18),
|
||
// ),
|
||
// Spacer(),
|
||
if (statusValue != "")
|
||
..._buildPlanPdf(isDesktop),
|
||
],
|
||
)
|
||
: Column(
|
||
children: [
|
||
Row(
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
mainAxisAlignment:
|
||
MainAxisAlignment.end,
|
||
children: [
|
||
..._buildApproverControls(
|
||
isDesktop,
|
||
),
|
||
if (statusValue != "")
|
||
..._buildPlanPdf(isDesktop),
|
||
],
|
||
),
|
||
Row(
|
||
crossAxisAlignment:
|
||
CrossAxisAlignment.start,
|
||
children: [
|
||
_buildTripName(isDesktop),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
),
|
||
// if (isStatusExpanded)
|
||
// Container(
|
||
// margin: isDesktop
|
||
// ? const EdgeInsets.only(left: 0, top: 0)
|
||
// : const EdgeInsets.only(left: 5, top: 2),
|
||
// padding: const EdgeInsets.all(12),
|
||
// width: isDesktop
|
||
// ? MediaQuery.of(context).size.width * 0.2
|
||
// : MediaQuery.of(context).size.width,
|
||
// decoration: BoxDecoration(
|
||
// color: Color(0xFFF5F5F5),
|
||
// border: Border.all(
|
||
// // color: Colors.grey.shade300,
|
||
// color: Colors.white,
|
||
// width: 0.2),
|
||
// borderRadius: BorderRadius.circular(8),
|
||
// // boxShadow: [
|
||
// // BoxShadow(
|
||
// // // color: Colors.grey.withAlpha(20),
|
||
// // color: Colors.grey.withAlpha(20),
|
||
// // spreadRadius: 1.5,
|
||
// // blurRadius: 7,
|
||
// // offset: Offset(0, 4), // shadow direction: bottom
|
||
// // ),
|
||
// // ],
|
||
// ),
|
||
// child: Column(
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
// children: [
|
||
// if (!hasApprovals)
|
||
// Center(
|
||
// child: Text(
|
||
// "--- No Approvals ---",
|
||
// style: TextStyle(
|
||
// fontFamily: "Archivo",
|
||
// fontSize: 11,
|
||
// fontWeight: FontWeight.w500,
|
||
// color: Colors.black87,
|
||
// ),
|
||
// )),
|
||
// for (int i = 0; i < planStatusList.length; i++) ...[
|
||
// if (planStatusList[i].entries.any((entry) =>
|
||
// entry.key.contains('status') &&
|
||
// entry.value != null &&
|
||
// entry.value.toString().isNotEmpty)) ...[
|
||
// _buildApprovalItem(
|
||
// "Approver ${i + 1}",
|
||
// planStatusList[i]
|
||
// .entries
|
||
// .firstWhere(
|
||
// (entry) => entry.key.contains('status'),
|
||
// orElse: () => MapEntry('', ''),
|
||
// )
|
||
// .value
|
||
// .toString(),
|
||
// ),
|
||
// SizedBox(height: 6),
|
||
// ],
|
||
// ],
|
||
// ],
|
||
// ),
|
||
// ),
|
||
if (isApproverRejected)
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Padding(
|
||
padding: EdgeInsets.only(top: 4, left: 8),
|
||
child: Text(
|
||
"Remarks : ",
|
||
style: TextStyle(
|
||
fontFamily: "Archivo",
|
||
fontWeight: FontWeight.w600,
|
||
fontSize: 11,
|
||
),
|
||
),
|
||
),
|
||
Expanded(
|
||
child: Padding(
|
||
padding: const EdgeInsets.only(
|
||
top: 0,
|
||
), // tweak if needed
|
||
child: TextFormField(
|
||
controller: _remarksController,
|
||
style: TextStyle(
|
||
fontFamily: "Archivo",
|
||
fontWeight: FontWeight.w600,
|
||
fontSize: 11,
|
||
),
|
||
decoration: InputDecoration(
|
||
hintText: "Please enter remarks...",
|
||
hintStyle: GoogleFonts.poppins(
|
||
fontSize: 13,
|
||
color: Colors.grey,
|
||
),
|
||
border: InputBorder.none,
|
||
isDense: true,
|
||
),
|
||
maxLines: null,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
// if (widget.isApprover || isStatusExpanded)
|
||
Divider(thickness: 0.1, color: Colors.blueGrey),
|
||
if (widget.isApprover) SizedBox(height: 5),
|
||
|
||
Text(
|
||
"Planning This Trip For*", // Your label
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 6),
|
||
isDesktop
|
||
? SingleChildScrollView(
|
||
scrollDirection: Axis.horizontal,
|
||
child: Row(children: _buildPlanTrip(isDesktop)),
|
||
)
|
||
: Row(children: _buildPlanTrip(isDesktop)),
|
||
SizedBox(height: 7),
|
||
|
||
// Text(
|
||
// otherUserName ?? userName ?? " ", // Your label
|
||
//
|
||
// style: GoogleFonts.poppins(
|
||
// fontSize: 11,
|
||
// fontWeight: FontWeight.w400,
|
||
// color: widget.layoutColor,
|
||
// ),
|
||
// ),
|
||
Text.rich(
|
||
TextSpan(
|
||
text: "Trip Planned User : ", // Static text
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w400,
|
||
color: Color(0xFF212121),
|
||
// color: Color(0xFF575A74), // Default color
|
||
),
|
||
children: [
|
||
TextSpan(
|
||
text:
|
||
otherUserName ??
|
||
userName ??
|
||
" ", // Dynamic username
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w400,
|
||
color:
|
||
widget
|
||
.layoutColor, // Change this to any color
|
||
// color: Colors.blueAccent, // Change this to any color
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
// Padding(
|
||
// padding: const EdgeInsets.all(8.0),
|
||
// child: Divider(
|
||
// color: Color(0xFFE6E7F5), // Change color
|
||
// thickness: 0.5,
|
||
// ),
|
||
// ),
|
||
SizedBox(height: 10),
|
||
|
||
// Row(
|
||
// children: [
|
||
// Expanded(
|
||
// child:
|
||
// ),
|
||
// ],
|
||
// ),
|
||
isDesktop
|
||
? Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: _buildTripRow(isMobile),
|
||
)
|
||
: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: _buildTripRow(isMobile),
|
||
),
|
||
SizedBox(height: 15),
|
||
|
||
isDesktop
|
||
? Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: _buildCostCenter(isDesktop),
|
||
)
|
||
: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: _buildCostCenter(isDesktop),
|
||
),
|
||
SizedBox(height: 8),
|
||
isDesktop
|
||
? Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
//
|
||
_buildDescriptionColumn(isDesktop),
|
||
SizedBox(width: 25),
|
||
|
||
if (hasExceptionalClassInUpdate ||
|
||
_excepntldescriptionController.text != "")
|
||
_buildNonDescriptionColumn(),
|
||
],
|
||
)
|
||
: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_buildDescriptionColumn(isDesktop),
|
||
SizedBox(height: 15),
|
||
if (hasExceptionalClassInUpdate)
|
||
_buildNonDescriptionColumn(),
|
||
],
|
||
),
|
||
|
||
// Padding(
|
||
// padding: const EdgeInsets.all(8.0),
|
||
// child: Divider(
|
||
// color: Color(0xFFE6E7F5), // Change color
|
||
// thickness: 0.5,
|
||
// ),
|
||
// ),
|
||
SizedBox(height: 20),
|
||
|
||
if (temporaryMessage != null)
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text(
|
||
// validationErrors["services"]!,
|
||
temporaryMessage!,
|
||
style: GoogleFonts.poppins(
|
||
color: Colors.red,
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w400,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
if (temporaryMessage != null) SizedBox(height: 10),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: DynamicItinerary(
|
||
key: dynamicItineraryKey,
|
||
flightScreenKey: flightScreenKey,
|
||
tripTypeNotifier: flightTripTypeNotifier,
|
||
hasAction: hasAction,
|
||
tripType: _selectedTripType,
|
||
apiData: apiData,
|
||
apiDataForClass: apiDataForClass,
|
||
apiCountryData: apiCountryData,
|
||
onItineraryUpdate: handleItineraryUpdate,
|
||
onExeptionalClass: handleExcentionalClass,
|
||
loginUser: selfId,
|
||
selectedPlanData: planData,
|
||
isViewMode: widget.isViewMode,
|
||
),
|
||
), // Wrap with Expanded if needed
|
||
],
|
||
),
|
||
SizedBox(height: 15),
|
||
],
|
||
);
|
||
|
||
// Row(
|
||
// children: [
|
||
// isDesktop
|
||
// ? Row(
|
||
// mainAxisAlignment: MainAxisAlignment.end,
|
||
// children: _buildSubmit(isDesktop),
|
||
// )
|
||
// : Row(
|
||
// mainAxisAlignment: MainAxisAlignment.center,
|
||
// children: _buildSubmit(isDesktop),
|
||
// )
|
||
// ],
|
||
// )
|
||
},
|
||
),
|
||
// replace with your full form column
|
||
);
|
||
}
|
||
|
||
// String? getSelectedCostCenterName() {
|
||
// if (selectedCostCenterId == null || apiCostData == null) return null;
|
||
// return apiCostData!.firstWhere(
|
||
// (item) => item['department_id'] == selectedCostCenterId,
|
||
// orElse: () => null,
|
||
// )?['name'];
|
||
// }
|
||
|
||
final List<Map<String, dynamic>> allTripTypes = [
|
||
{"dropdown_key": "1", "dropdown_value": "Domestic"},
|
||
{"dropdown_key": "2", "dropdown_value": "International"},
|
||
];
|
||
|
||
List<Map<String, dynamic>> getFilteredTripTypes() {
|
||
if (!showDomestic && !showInternational) return [];
|
||
|
||
return allTripTypes.where((item) {
|
||
if (item['dropdown_key'] == "1" && showDomestic) return true;
|
||
if (item['dropdown_key'] == "2" && showInternational) return true;
|
||
return false;
|
||
}).toList();
|
||
}
|
||
|
||
List<Widget> _buildTripRow(bool isMobile) {
|
||
List<dynamic> purposeList = apiData?['plan_is_billable'] ?? [];
|
||
|
||
List<Map<String, dynamic>> tripTypeList = [
|
||
{"dropdown_key": "1", "dropdown_value": "Domestic"},
|
||
{"dropdown_key": "2", "dropdown_value": "International"},
|
||
];
|
||
|
||
return [
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Trip Type *", // Your label
|
||
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
|
||
CustomTextFieldWrapper(
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
// isFocused: focusStates["trip_typeFocused"] ?? false,
|
||
width:
|
||
widget.isDesktop
|
||
? MediaQuery.of(context).size.width * 0.32
|
||
: MediaQuery.of(context).size.width * 0.88,
|
||
isDesktop: widget.isDesktop,
|
||
child: SizedBox(
|
||
height: 35,
|
||
// width: double.infinity,
|
||
width: double.infinity,
|
||
child: Focus(
|
||
focusNode: focusNodes["trip_typeFocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["trip_typeFocused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["trip_typeFocusNode"]?.requestFocus();
|
||
},
|
||
child: DropdownSearch<Map<String, dynamic>>(
|
||
popupProps: PopupProps.menu(
|
||
showSearchBox: false, // Optionally enable search
|
||
fit: FlexFit.loose,
|
||
|
||
menuProps: const MenuProps(backgroundColor: Colors.white),
|
||
|
||
itemBuilder: (context, item, isSelected) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 8,
|
||
),
|
||
child: Text(
|
||
item['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12, // 👈 Smaller font size
|
||
color: Colors.black,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
// border: InputBorder.none, // No underline
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["trip_typeFocused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["trip_typeFocused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(color: layoutColor!, width: 1),
|
||
),
|
||
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null || selectedItem.isEmpty) {
|
||
return Text(
|
||
"Select Trip Type",
|
||
style: GoogleFonts.poppins(
|
||
color: Colors.grey,
|
||
fontSize: 13,
|
||
),
|
||
);
|
||
}
|
||
return Text(
|
||
selectedItem['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.black,
|
||
),
|
||
);
|
||
},
|
||
selectedItem: tripTypeList.firstWhere(
|
||
(item) => item['dropdown_key'] == _selectedTripType,
|
||
orElse: () => {},
|
||
),
|
||
itemAsString: (item) => item['dropdown_value'] ?? '',
|
||
items: getFilteredTripTypes(),
|
||
|
||
// items:
|
||
// [
|
||
// {"dropdown_key": "1", "dropdown_value": "Domestic"},
|
||
// {"dropdown_key": "2", "dropdown_value": "International"},
|
||
// ].map<Map<String, dynamic>>((item) {
|
||
// return Map<String, dynamic>.from(item);
|
||
// }).toList(),
|
||
onChanged:
|
||
widget.isViewMode
|
||
? null
|
||
: (Map<String, dynamic>? newItem) {
|
||
if (newItem != null) {
|
||
setState(() {
|
||
_selectedTripType =
|
||
newItem['dropdown_key'].toString();
|
||
// Notify FlightScreen
|
||
flightTripTypeNotifier.value =
|
||
_selectedTripType;
|
||
print(
|
||
"flightTripTypeNotifier- ${flightTripTypeNotifier.value}",
|
||
);
|
||
fetchTrainFlightClass(
|
||
int.parse(_selectedTripType!),
|
||
);
|
||
dynamicItineraryKey.currentState
|
||
?.updateSelectedServices();
|
||
});
|
||
}
|
||
},
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
// isMobile
|
||
// ? SingleChildScrollView(
|
||
// scrollDirection: Axis.horizontal,
|
||
// child: Row(
|
||
// children: _buildTripType(isMobile),
|
||
// ),
|
||
// )
|
||
// : Row(
|
||
// children: _buildTripType(isMobile),
|
||
// ),
|
||
if (validationErrors["trip_type"] != null)
|
||
Padding(
|
||
padding: EdgeInsets.only(top: 4),
|
||
child: Text(
|
||
validationErrors["trip_type"]!,
|
||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(width: 25, height: 5),
|
||
|
||
// SizedBox(
|
||
// width: 25,
|
||
// height: 5,
|
||
// ),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Is Billable ", // Your label
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
|
||
CustomTextFieldWrapper(
|
||
// isFocused: focusStates["is_billableFocused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
width:
|
||
widget.isDesktop
|
||
? MediaQuery.of(context).size.width * 0.2
|
||
: null,
|
||
|
||
isDesktop: widget.isDesktop,
|
||
child: SizedBox(
|
||
height: 35,
|
||
width: double.infinity,
|
||
child: Focus(
|
||
focusNode: focusNodes["is_billableFocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["is_billableFocused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["is_billableFocusNode"]?.requestFocus();
|
||
},
|
||
child: DropdownSearch<Map<String, dynamic>>(
|
||
popupProps: PopupProps.menu(
|
||
showSearchBox: false,
|
||
fit: FlexFit.loose,
|
||
menuProps: const MenuProps(backgroundColor: Colors.white),
|
||
itemBuilder: (context, item, isSelected) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 8,
|
||
),
|
||
child: Text(
|
||
item['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.black,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["is_billableFocused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["is_billableFocused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(color: layoutColor!, width: 1),
|
||
),
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
selectedItem: purposeList
|
||
.cast<Map<String, dynamic>>()
|
||
.firstWhere(
|
||
(item) => item['dropdown_key'] == _selectedIsBillable,
|
||
orElse: () => {},
|
||
),
|
||
dropdownButtonProps: const DropdownButtonProps(
|
||
icon: Icon(Icons.arrow_drop_down),
|
||
),
|
||
// itemAsString: (item) => item['dropdown_value'] ?? '',
|
||
// items: purposeList.cast<Map<String, dynamic>>(),
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null || selectedItem.isEmpty) {
|
||
return Text(
|
||
"Select Billable", // fallback text
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.grey,
|
||
),
|
||
);
|
||
}
|
||
return Text(
|
||
selectedItem['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.black,
|
||
),
|
||
);
|
||
},
|
||
|
||
items:
|
||
purposeList.map<Map<String, dynamic>>((item) {
|
||
return Map<String, dynamic>.from(
|
||
item,
|
||
); // Ensuring each item is properly cast
|
||
}).toList(),
|
||
onChanged:
|
||
widget.isViewMode
|
||
? null
|
||
: (Map<String, dynamic>? newItem) {
|
||
if (newItem != null) {
|
||
setState(() {
|
||
_selectedIsBillable =
|
||
newItem['dropdown_key'].toString();
|
||
});
|
||
}
|
||
},
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
|
||
// Row(
|
||
// children: purposeList.map<Widget>((item) {
|
||
// final isSelected =
|
||
// _selectedIsBillable == item['dropdown_key'].toString();
|
||
//
|
||
// return Padding(
|
||
// padding: const EdgeInsets.symmetric(horizontal: 5),
|
||
// child: CustomTextFieldWrapper(
|
||
// color: Color(0xFFF5F5F5),
|
||
// padding:
|
||
// const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||
// layoutColor: widget.layoutColor,
|
||
// width: item['dropdown_key'] == 'some_key' ? 185 : 125,
|
||
// borderRadius: BorderRadius.circular(25),
|
||
// isFocused: isSelected,
|
||
// isDesktop: widget.isDesktop,
|
||
// child: GestureDetector(
|
||
// onTap: widget.isViewMode
|
||
// ? null
|
||
// : () {
|
||
// setState(() {
|
||
// _selectedIsBillable =
|
||
// item['dropdown_key'].toString();
|
||
// });
|
||
// print("SELECBILL - $_selectedIsBillable");
|
||
// },
|
||
// child: Row(
|
||
// mainAxisSize: MainAxisSize.min,
|
||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
// children: [
|
||
// Text(
|
||
// item['dropdown_value'] ?? '',
|
||
// style: TextStyle(
|
||
// color: isSelected ? Colors.white : Colors.black,
|
||
// fontWeight: isSelected ? FontWeight.w500 : null,
|
||
// fontSize: 13),
|
||
// ),
|
||
// const SizedBox(width: 8),
|
||
// Container(
|
||
// width: 16,
|
||
// height: 16,
|
||
// decoration: BoxDecoration(
|
||
// borderRadius: BorderRadius.circular(4),
|
||
// border: Border.all(
|
||
// color: isSelected ? Colors.white : Colors.black,
|
||
// width: isSelected ? 2 : 1,
|
||
// ),
|
||
// ),
|
||
// child: isSelected
|
||
// ? Icon(Icons.rectangle,
|
||
// size: 8, color: Colors.white)
|
||
// : null,
|
||
// ),
|
||
// ],
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// );
|
||
// }).toList(),
|
||
// ),
|
||
],
|
||
),
|
||
];
|
||
}
|
||
|
||
List<Widget> _buildCostCenter(bool isDesktop) {
|
||
// if (apiData == null) {
|
||
// return Center(child: CircularProgressIndicator()); // Show loading indicator
|
||
// }
|
||
|
||
// 'plan_purpose_of_travel' Starts ------------------------------------------------------
|
||
|
||
// List<dynamic> purposeList = apiData?['plan_purpose_of_travel'] ?? [];
|
||
List<Map<String, dynamic>> purposeList = List<Map<String, dynamic>>.from(
|
||
apiData?['plan_purpose_of_travel'] ?? [],
|
||
);
|
||
|
||
List<DropdownMenuItem<String>> dropdownItems =
|
||
purposeList
|
||
.map(
|
||
(item) => DropdownMenuItem<String>(
|
||
// value: item['dropdown_key'],
|
||
value: item['dropdown_key']?.toString(),
|
||
// value: item['dropdown_key'].toString(),
|
||
child: Text(item['dropdown_value']),
|
||
),
|
||
)
|
||
.toList();
|
||
|
||
if (dropdownItems.isEmpty) {
|
||
dropdownItems.add(
|
||
DropdownMenuItem<String>(
|
||
// value: null,
|
||
value: "1",
|
||
child: Text(
|
||
"No options available",
|
||
style: GoogleFonts.poppins(color: Colors.grey),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
selectedPurpose ??=
|
||
dropdownItems.isNotEmpty
|
||
? dropdownItems.first.value.toString()
|
||
: "No options";
|
||
|
||
print(
|
||
"Dropdown Purpose List: ${dropdownItems.map((e) => e.value).toList()}",
|
||
);
|
||
print("Selected Purpose: $selectedPurpose");
|
||
|
||
// 'plan_functional_department' Starts ---------------------------------------------
|
||
|
||
// List<dynamic> funcDeptList = apiData?['plan_functional_department'] ?? [];
|
||
List<Map<String, dynamic>> funcDeptList = List<Map<String, dynamic>>.from(
|
||
apiData?['plan_functional_department'] ?? [],
|
||
);
|
||
|
||
List<DropdownMenuItem<String>> dropdownFuncDeptItems =
|
||
funcDeptList
|
||
.map(
|
||
(item) => DropdownMenuItem<String>(
|
||
// value: item['dropdown_key'],
|
||
value: item['dropdown_key']?.toString(),
|
||
child: Text(item['dropdown_value']),
|
||
),
|
||
)
|
||
.toList();
|
||
|
||
if (dropdownFuncDeptItems.isEmpty) {
|
||
dropdownFuncDeptItems.add(
|
||
DropdownMenuItem<String>(
|
||
// value: null,
|
||
value: "1",
|
||
child: Text(
|
||
"No options available",
|
||
style: TextStyle(color: Colors.grey),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : null;
|
||
selectedFuncDept ??=
|
||
dropdownFuncDeptItems.isNotEmpty
|
||
? dropdownFuncDeptItems.first.value.toString()
|
||
: "No options";
|
||
|
||
print(
|
||
"Dropdown Functional Department List: ${dropdownFuncDeptItems.map((e) => e.value).toList()}",
|
||
);
|
||
print("Selected Functional Department: $selectedFuncDept");
|
||
|
||
return [
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Cost Center *", // Your label
|
||
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldWrapper(
|
||
width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null,
|
||
// isFocused: focusStates["cost_center_idFocused"] ??
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
|
||
isDesktop: widget.isDesktop,
|
||
child: SizedBox(
|
||
height: 35,
|
||
width: double.infinity,
|
||
child:
|
||
apiCostData == null
|
||
? Center(
|
||
child: Transform.scale(
|
||
scale: 0.5,
|
||
child: CircularProgressIndicator(),
|
||
),
|
||
)
|
||
: Focus(
|
||
focusNode: focusNodes["cost_center_idFocusNode"],
|
||
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["cost_center_idFocused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["cost_center_idFocusNode"]
|
||
?.requestFocus();
|
||
},
|
||
child: DropdownSearch<String>(
|
||
selectedItem: costCenterMap[selectedCostCenterId],
|
||
enabled: !widget.isViewMode,
|
||
popupProps: PopupProps.menu(
|
||
showSearchBox: true,
|
||
fit: FlexFit.loose,
|
||
constraints: BoxConstraints(maxHeight: 250),
|
||
searchFieldProps: TextFieldProps(
|
||
decoration: InputDecoration(
|
||
hintText: "Search Cost Center...",
|
||
// hintStyle: TextStyle(fontSize: 13, color: Colors.grey),
|
||
hintStyle: GoogleFonts.poppins(
|
||
fontSize: 13,
|
||
color: Colors.grey,
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
),
|
||
),
|
||
style: GoogleFonts.poppins(fontSize: 13),
|
||
),
|
||
menuProps: MenuProps(
|
||
backgroundColor: Colors.white,
|
||
),
|
||
itemBuilder:
|
||
(context, item, isSelected) => Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 8.0,
|
||
vertical: 6.0,
|
||
),
|
||
child: Text(
|
||
item,
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 13,
|
||
), // 👈 Set your desired text size here
|
||
),
|
||
),
|
||
),
|
||
|
||
items: costCenterMap.values.toList(), // just names
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["cost_center_idFocused"] ??
|
||
false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["cost_center_idFocused"] ??
|
||
false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color: layoutColor!,
|
||
width: 1,
|
||
),
|
||
),
|
||
// contentPadding: EdgeInsets.symmetric(horizontal: 1),
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null ||
|
||
selectedItem.isEmpty) {
|
||
return Text(
|
||
"Select Purpose", // fallback text
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.grey,
|
||
),
|
||
);
|
||
}
|
||
return Text(
|
||
selectedItem ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.black,
|
||
),
|
||
);
|
||
},
|
||
// dropdownBuilder: (context, selectedItem) => Align(
|
||
// alignment: Alignment.centerLeft,
|
||
// child: Text(
|
||
// selectedItem ?? "Select",
|
||
// style: TextStyle(fontSize: 12),
|
||
// ),
|
||
// ),
|
||
onChanged: (String? newValue) {
|
||
setState(() {
|
||
selectedCostCenterId =
|
||
costCenterMap.entries
|
||
.firstWhere(
|
||
(entry) => entry.value == newValue,
|
||
)
|
||
.key;
|
||
});
|
||
},
|
||
),
|
||
),
|
||
),
|
||
),
|
||
|
||
// child: SizedBox(
|
||
// height: 45, // Set appropriate height
|
||
// child: DropdownButtonFormField<String>(
|
||
// value: selectedCostCenterId,
|
||
// style: TextStyle(fontSize: 12),
|
||
// decoration: InputDecoration(
|
||
// border: InputBorder.none,
|
||
// contentPadding:
|
||
// EdgeInsets.symmetric(horizontal: 10), // Proper padding
|
||
// ),
|
||
// onChanged: widget.isViewMode
|
||
// ? null
|
||
// : (newValue) {
|
||
// setState(() {
|
||
// selectedCostCenterId = newValue;
|
||
// });
|
||
// },
|
||
// items: apiCostData?.map<DropdownMenuItem<String>>((item) {
|
||
// return DropdownMenuItem(
|
||
// value: item['department_id'], // ID as value
|
||
// child: Text(item['name'] ?? "Unknown"),
|
||
// );
|
||
// }).toList(),
|
||
// ),
|
||
// ),
|
||
),
|
||
if (validationErrors["cost_center_id"] != null)
|
||
Padding(
|
||
padding: EdgeInsets.only(top: 4),
|
||
child: Text(
|
||
validationErrors["cost_center_id"]!,
|
||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(width: 25, height: 5),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Purpose of Trip *", // Your label
|
||
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldWrapper(
|
||
width:
|
||
isDesktop
|
||
? MediaQuery.of(context).size.width * 0.15
|
||
: MediaQuery.of(context).size.width * 0.88,
|
||
// isFocused: focusStates["purpose_of_travelFocused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
|
||
isDesktop: widget.isDesktop,
|
||
child: SizedBox(
|
||
height: 35, // Set appropriate height
|
||
|
||
child:
|
||
apiData == null
|
||
? Center(
|
||
child: CircularProgressIndicator(),
|
||
) // Show loading inside dropdown
|
||
: Focus(
|
||
focusNode: focusNodes["purpose_of_travelFocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["purpose_of_travelFocused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["purpose_of_travelFocusNode"]
|
||
?.requestFocus();
|
||
},
|
||
child: DropdownSearch<Map<String, dynamic>>(
|
||
selectedItem: purposeList.firstWhere(
|
||
(item) =>
|
||
item['dropdown_key'].toString() ==
|
||
selectedPurpose,
|
||
orElse:
|
||
() => <String, dynamic>{}, // ✅ Safe fallback
|
||
),
|
||
items: purposeList,
|
||
itemAsString:
|
||
(Map<String, dynamic> item) =>
|
||
item['dropdown_value'],
|
||
popupProps: PopupProps.menu(
|
||
showSearchBox: true,
|
||
fit: FlexFit.loose,
|
||
constraints: BoxConstraints(maxHeight: 250),
|
||
searchFieldProps: TextFieldProps(
|
||
decoration: InputDecoration(
|
||
hintText: "Search Purpose...",
|
||
hintStyle: GoogleFonts.poppins(
|
||
fontSize: 13,
|
||
color: Colors.grey,
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
),
|
||
),
|
||
style: GoogleFonts.poppins(fontSize: 13),
|
||
),
|
||
menuProps: MenuProps(
|
||
backgroundColor: Colors.white,
|
||
),
|
||
itemBuilder:
|
||
(context, item, isSelected) => Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 8.0,
|
||
vertical: 6.0,
|
||
),
|
||
child: Text(
|
||
item['dropdown_value'],
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 13,
|
||
), // 👈 Set your desired text size here
|
||
),
|
||
),
|
||
),
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["purpose_of_travelFocused"] ??
|
||
false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["purpose_of_travelFocused"] ??
|
||
false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color: layoutColor!,
|
||
width: 1,
|
||
),
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
onChanged:
|
||
widget.isViewMode
|
||
? null
|
||
: (Map<String, dynamic>? newValue) {
|
||
setState(() {
|
||
selectedPurpose =
|
||
newValue?['dropdown_key']
|
||
.toString();
|
||
});
|
||
print(
|
||
"selectedPurpose - $selectedPurpose",
|
||
);
|
||
},
|
||
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null ||
|
||
selectedItem.isEmpty) {
|
||
return Text(
|
||
"Select Purpose", // fallback text
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 13,
|
||
color: Colors.grey,
|
||
),
|
||
);
|
||
}
|
||
return Text(
|
||
selectedItem['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize:
|
||
12, // 👈 Small font size for selected item
|
||
color: Colors.black,
|
||
),
|
||
);
|
||
},
|
||
// dropdownBuilder: (context, selectedItem) => Align(
|
||
// alignment: Alignment.centerLeft,
|
||
// child: Text(
|
||
// selectedItem?['dropdown_value'] ?? '',
|
||
// style: TextStyle(fontSize: 12),
|
||
// ),
|
||
// ),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
if (validationErrors["purpose_of_travel"] != null)
|
||
Padding(
|
||
padding: EdgeInsets.only(top: 4),
|
||
child: Text(
|
||
validationErrors["purpose_of_travel"]!,
|
||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
|
||
),
|
||
),
|
||
|
||
// CustomTextFieldWrapper(
|
||
// isFocused: false, // Dropdown doesn't use focus
|
||
// isDesktop: widget.isDesktop,
|
||
// child: SizedBox(
|
||
// height: 45, // Set appropriate height
|
||
// child: apiData == null
|
||
// ? Center(
|
||
// child:
|
||
// CircularProgressIndicator()) // Show loading inside dropdown
|
||
// : DropdownButtonFormField<String>(
|
||
// value: selectedPurpose,
|
||
// style: TextStyle(fontSize: 12),
|
||
// decoration: InputDecoration(
|
||
// border: InputBorder.none,
|
||
// contentPadding: EdgeInsets.symmetric(
|
||
// horizontal: 10), // Proper padding
|
||
// ),
|
||
// onChanged: widget.isViewMode
|
||
// ? null
|
||
// : purposeList.isNotEmpty
|
||
// ? (newValue) {
|
||
// setState(() {
|
||
// selectedPurpose = newValue;
|
||
// });
|
||
// print(
|
||
// "selectedPurpose - $selectedPurpose");
|
||
// }
|
||
// : null,
|
||
// items: dropdownItems,
|
||
// ),
|
||
// ),
|
||
// ),
|
||
],
|
||
),
|
||
SizedBox(width: 25, height: 5),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Functional Department", // Your label
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldWrapper(
|
||
// isFocused: focusStates["functional_departmentFocused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
isDesktop: widget.isDesktop,
|
||
width:
|
||
isDesktop
|
||
? MediaQuery.of(context).size.width * 0.2
|
||
: MediaQuery.of(context).size.width * 0.88,
|
||
// width: MediaQuery.of(context).size.width * 0.2,
|
||
child: SizedBox(
|
||
height: 35, // Set appropriate height
|
||
child:
|
||
apiData == null
|
||
? Center(
|
||
child: CircularProgressIndicator(),
|
||
) // Show loading inside dropdown
|
||
: Focus(
|
||
focusNode: focusNodes["functional_departmentFocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["functional_departmentFocused"] =
|
||
hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["functional_departmentFocusNode"]
|
||
?.requestFocus();
|
||
},
|
||
child: DropdownSearch<Map<String, dynamic>>(
|
||
selectedItem: funcDeptList.firstWhere(
|
||
(item) =>
|
||
item['dropdown_key'].toString() ==
|
||
selectedFuncDept,
|
||
orElse:
|
||
() => <String, dynamic>{}, // ✅ Safe fallback
|
||
),
|
||
items: funcDeptList,
|
||
itemAsString:
|
||
(Map<String, dynamic> item) =>
|
||
item['dropdown_value'],
|
||
popupProps: PopupProps.menu(
|
||
showSearchBox: true,
|
||
fit: FlexFit.loose,
|
||
constraints: BoxConstraints(maxHeight: 250),
|
||
searchFieldProps: TextFieldProps(
|
||
decoration: InputDecoration(
|
||
hintText: "Search department...",
|
||
hintStyle: GoogleFonts.poppins(
|
||
fontSize: 13,
|
||
color: Colors.grey,
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
),
|
||
),
|
||
style: GoogleFonts.poppins(
|
||
fontSize:
|
||
13, // 👈 Small font size for selected item
|
||
),
|
||
),
|
||
menuProps: MenuProps(
|
||
backgroundColor: Colors.white,
|
||
),
|
||
itemBuilder:
|
||
(context, item, isSelected) => Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 8.0,
|
||
vertical: 6.0,
|
||
),
|
||
child: Text(
|
||
item['dropdown_value'],
|
||
style: GoogleFonts.poppins(
|
||
fontSize:
|
||
13, // 👈 Small font size for selected item
|
||
), // 👈 Set your desired text size here
|
||
),
|
||
),
|
||
),
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["functional_departmentFocused"] ??
|
||
false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["functional_departmentFocused"] ??
|
||
false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color: layoutColor!,
|
||
width: 1,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
onChanged:
|
||
widget.isViewMode
|
||
? null
|
||
: (Map<String, dynamic>? newValue) {
|
||
dynamic val =
|
||
newValue?['dropdown_value']
|
||
.toString();
|
||
setState(() {
|
||
selectedFuncDept =
|
||
newValue?['dropdown_key']
|
||
.toString();
|
||
|
||
if (val ==
|
||
" Others (Kindly enter SO number )") {
|
||
hasSoNumber = true;
|
||
} else {
|
||
hasSoNumber = false;
|
||
_soNumberController.text = "";
|
||
}
|
||
});
|
||
|
||
print(
|
||
"selectedFuncDept1 - ${newValue?['dropdown_value'].toString()}",
|
||
);
|
||
print(
|
||
"selectedFuncDept - $selectedFuncDept",
|
||
);
|
||
},
|
||
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null ||
|
||
selectedItem.isEmpty) {
|
||
return const Text(
|
||
"Select Purpose", // fallback text
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: Colors.grey,
|
||
),
|
||
);
|
||
}
|
||
return Text(
|
||
selectedItem['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize:
|
||
12, // 👈 Small font size for selected item
|
||
color: Colors.black,
|
||
),
|
||
);
|
||
},
|
||
// dropdownBuilder: (context, selectedItem) => Align(
|
||
// alignment: Alignment.centerLeft,
|
||
// child: Text(
|
||
// selectedItem?['dropdown_value'] ?? '',
|
||
// style: TextStyle(fontSize: 12),
|
||
// ),
|
||
// ),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
if (validationErrors["functional_department"] != null)
|
||
Padding(
|
||
padding: EdgeInsets.only(top: 4),
|
||
child: Text(
|
||
validationErrors["functional_department"]!,
|
||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(width: 25, height: 5),
|
||
|
||
hasSoNumber
|
||
? Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"SO Number *", // Your label
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldWrapper(
|
||
isFocused: focusStates["so_numberFocused"] ?? false,
|
||
// isFocused: _isdescriptionFocused,
|
||
width:
|
||
widget.isDesktop
|
||
? MediaQuery.of(context).size.width * 0.2
|
||
: null,
|
||
isDesktop: widget.isDesktop,
|
||
child: SizedBox(
|
||
height: 35,
|
||
child: TextField(
|
||
focusNode: focusNodes["so_numberFocusNode"],
|
||
controller: _soNumberController,
|
||
style: TextStyle(fontSize: 12),
|
||
decoration: InputDecoration(
|
||
labelText: "SO Number",
|
||
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||
border: InputBorder.none,
|
||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
if (validationErrors["so_number"] != null)
|
||
Padding(
|
||
padding: EdgeInsets.only(top: 4),
|
||
child: Text(
|
||
validationErrors["so_number"]!,
|
||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
|
||
),
|
||
),
|
||
],
|
||
)
|
||
: SizedBox.shrink(),
|
||
];
|
||
}
|
||
|
||
List<Widget> _buildPlanTrip(bool isDesktop) {
|
||
List<Map<String, String>> options = [
|
||
{"title": "Self", "value": "Option 1"},
|
||
{"title": "Other Employee", "value": "Option 2"},
|
||
{"title": "Others (Non Employee)", "value": "Option 3"},
|
||
];
|
||
|
||
return [
|
||
CustomTextFieldWrapper(
|
||
// isFocused: focusStates["trip_plannedFocused"] ?? false,
|
||
width:
|
||
widget.isDesktop
|
||
? MediaQuery.of(context).size.width * 0.32
|
||
: MediaQuery.of(context).size.width * 0.88,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
isDesktop: isDesktop,
|
||
layoutColor: widget.layoutColor,
|
||
child: SizedBox(
|
||
height: 35,
|
||
width: double.infinity,
|
||
child: Focus(
|
||
focusNode: focusNodes["trip_plannedFocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["trip_plannedFocused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["trip_plannedFocusNode"]?.requestFocus();
|
||
},
|
||
child: DropdownSearch<String>(
|
||
selectedItem:
|
||
options.firstWhere(
|
||
(opt) => opt['value'] == _selectedOption,
|
||
)['title'],
|
||
enabled: !widget.isViewMode,
|
||
items: options.map((opt) => opt['title']!).toList(),
|
||
popupProps: PopupProps.menu(
|
||
showSearchBox: false,
|
||
menuProps: const MenuProps(backgroundColor: Colors.white),
|
||
|
||
constraints: BoxConstraints(maxHeight: 100),
|
||
itemBuilder:
|
||
(context, item, isSelected) => Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 8.0,
|
||
vertical: 6.0,
|
||
),
|
||
child: Text(
|
||
item,
|
||
style: GoogleFonts.poppins(fontSize: 13),
|
||
),
|
||
),
|
||
),
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["trip_typeFocused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["trip_plannedFocused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(color: layoutColor!, width: 1),
|
||
),
|
||
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
dropdownBuilder: (context, selectedItem) {
|
||
return Text(
|
||
selectedItem ?? "Select Purpose",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: selectedItem == null ? Colors.grey : Colors.black,
|
||
),
|
||
);
|
||
},
|
||
onChanged: (String? newTitle) {
|
||
if (newTitle == null) return;
|
||
final selected = options.firstWhere(
|
||
(opt) => opt["title"] == newTitle,
|
||
);
|
||
setState(() {
|
||
_selectedOption = selected["value"]!;
|
||
if (_selectedOption == "Option 2" ||
|
||
_selectedOption == "Option 3") {
|
||
_showInputDialog(selected["title"]!);
|
||
} else if (_selectedOption == "Option 1") {
|
||
otherUserName = userName;
|
||
}
|
||
});
|
||
},
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
];
|
||
}
|
||
|
||
List<Widget> _buildPlanTrip1(bool isDesktop) {
|
||
List<Map<String, String>> options = [
|
||
{"title": "Self", "value": "Option 1"},
|
||
{"title": "Other Employee", "value": "Option 2"},
|
||
{"title": "Others (Non Employee)", "value": "Option 3"},
|
||
];
|
||
|
||
print(" layoutColor: ${widget.layoutColor}");
|
||
return options.map((option) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 0),
|
||
child: CustomTextFieldWrapper(
|
||
// color: Color(0xFFF4F4FB),
|
||
color: Color(0xFFF5F5F5),
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||
layoutColor: widget.layoutColor,
|
||
width:
|
||
option["value"] == "Option 2"
|
||
? 185
|
||
: 125, // Adjust width conditionally
|
||
borderRadius: BorderRadius.circular(10),
|
||
isFocused: _selectedOption == option["value"],
|
||
isDesktop: isDesktop,
|
||
child: GestureDetector(
|
||
onTap:
|
||
widget.isViewMode
|
||
? null
|
||
: () {
|
||
setState(() {
|
||
_selectedOption = option["value"]!;
|
||
if (option["value"] == 'Option 2' ||
|
||
option["value"] == 'Option 3') {
|
||
_showInputDialog(option["title"]!);
|
||
}
|
||
});
|
||
},
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
option["title"]!,
|
||
style: TextStyle(
|
||
fontSize: 13,
|
||
color:
|
||
_selectedOption == option["value"]
|
||
? Colors.white
|
||
: Colors.black,
|
||
fontWeight:
|
||
_selectedOption == option["value"]
|
||
? FontWeight.w500
|
||
: null,
|
||
),
|
||
),
|
||
Container(
|
||
width: 15,
|
||
height: 15,
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.rectangle,
|
||
// color: _selectedOption == option["value"]
|
||
// ? Colors.blueAccent
|
||
// : Colors.transparent,
|
||
borderRadius: BorderRadius.circular(4), // Rounded rectangle
|
||
border: Border.all(
|
||
color:
|
||
_selectedOption == option["value"]
|
||
? Colors.white
|
||
: Colors.black,
|
||
width: _selectedOption == option["value"] ? 2 : 1,
|
||
),
|
||
),
|
||
child:
|
||
_selectedOption == option["value"]
|
||
? Icon(Icons.rectangle, size: 8, color: Colors.white)
|
||
: null, // Add checkmark if selected
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}).toList();
|
||
}
|
||
|
||
List<Widget> _buildTripType(bool isMobile) {
|
||
return [
|
||
if (showDomestic == true)
|
||
GestureDetector(
|
||
onTap:
|
||
widget.isViewMode
|
||
? null
|
||
: () {
|
||
setState(() {
|
||
_selectedTripType = "1";
|
||
fetchTrainFlightClass(1);
|
||
|
||
dynamicItineraryKey.currentState
|
||
?.updateSelectedServices();
|
||
});
|
||
},
|
||
child: CustomTextFieldWrapper(
|
||
color: Color(0xFFF4F4FB),
|
||
layoutColor: widget.layoutColor,
|
||
borderRadius: BorderRadius.circular(25),
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||
width: 130,
|
||
isFocused: _selectedTripType == "1",
|
||
isDesktop: widget.isDesktop,
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
"Domestic",
|
||
style: TextStyle(
|
||
color:
|
||
_selectedTripType == "1" ? Colors.white : Colors.black,
|
||
fontWeight:
|
||
_selectedTripType == "1" ? FontWeight.w500 : null,
|
||
fontSize: 13,
|
||
),
|
||
),
|
||
Container(
|
||
width: 15,
|
||
height: 15,
|
||
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.rectangle,
|
||
// color: _selectedOption == option["value"]
|
||
// ? Colors.blueAccent
|
||
// : Colors.transparent,
|
||
borderRadius: BorderRadius.circular(4), // Rounded rectangle
|
||
border: Border.all(
|
||
color:
|
||
_selectedTripType == "1"
|
||
? Colors.white
|
||
: Colors.black,
|
||
width: _selectedTripType == "1" ? 2 : 1,
|
||
),
|
||
),
|
||
child:
|
||
_selectedTripType == "1"
|
||
? Icon(Icons.rectangle, size: 8, color: Colors.white)
|
||
: null, // Add checkmark if selected
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
SizedBox(width: 20),
|
||
if (showInternational)
|
||
GestureDetector(
|
||
onTap:
|
||
widget.isViewMode
|
||
? null
|
||
: () {
|
||
setState(() {
|
||
_selectedTripType = "2";
|
||
fetchTrainFlightClass(2);
|
||
dynamicItineraryKey.currentState
|
||
?.updateSelectedServices();
|
||
});
|
||
},
|
||
child: CustomTextFieldWrapper(
|
||
color: Color(0xFFF4F4FB),
|
||
layoutColor: widget.layoutColor,
|
||
borderRadius: BorderRadius.circular(25),
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||
width: 150,
|
||
// padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
||
isFocused: _selectedTripType == "2",
|
||
isDesktop: widget.isDesktop,
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
"International",
|
||
style: TextStyle(
|
||
fontSize: 13,
|
||
color:
|
||
_selectedTripType == "2" ? Colors.white : Colors.black,
|
||
fontWeight:
|
||
_selectedTripType == "2" ? FontWeight.w500 : null,
|
||
),
|
||
),
|
||
Container(
|
||
width: 15,
|
||
height: 15,
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.rectangle,
|
||
// color: _selectedOption == option["value"]
|
||
// ? Colors.blueAccent
|
||
// : Colors.transparent,
|
||
borderRadius: BorderRadius.circular(4), // Rounded rectangle
|
||
border: Border.all(
|
||
color:
|
||
_selectedTripType == "2"
|
||
? Colors.white
|
||
: Colors.black,
|
||
width: _selectedTripType == "2" ? 2 : 1,
|
||
),
|
||
),
|
||
child:
|
||
_selectedTripType == "2"
|
||
? Icon(Icons.rectangle, size: 8, color: Colors.white)
|
||
: null, // Add checkmark if selected
|
||
),
|
||
],
|
||
),
|
||
|
||
// RadioListTile<String>(
|
||
// activeColor: Colors.blueAccent,
|
||
// contentPadding: EdgeInsets.zero,
|
||
// dense: true,
|
||
// title: Text("International"),
|
||
// value: "2",
|
||
// groupValue: _selectedTripType,
|
||
// onChanged: widget.isViewMode
|
||
// ? null
|
||
// : (value) {
|
||
// setState(() {
|
||
// _selectedTripType = value!;
|
||
// });
|
||
// },
|
||
// ),
|
||
),
|
||
),
|
||
];
|
||
}
|
||
|
||
void _showExceptionalReasonModal(BuildContext context) {
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: false, // prevent closing by tapping outside
|
||
builder: (context) {
|
||
return Dialog(
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(10),
|
||
),
|
||
child: Container(
|
||
padding: const EdgeInsets.all(16),
|
||
// decoration: BoxDecoration(
|
||
// border: Border.all(
|
||
// color: Colors.grey, // or any color you prefer
|
||
// width: 1.0,
|
||
// ),
|
||
// borderRadius: BorderRadius.circular(10),
|
||
// ),
|
||
color: Colors.white,
|
||
width: 400,
|
||
// width: MediaQuery.of(context).size.width * 0.5,
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Text(
|
||
"Reason for travel policy exception",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
TextField(
|
||
controller: _excepntldescriptionController,
|
||
maxLines: 3,
|
||
decoration: InputDecoration(
|
||
hintText: "Enter reason...",
|
||
hintStyle: GoogleFonts.poppins(fontSize: 10),
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(
|
||
10,
|
||
), // 👈 Rounded border
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(10),
|
||
borderSide: BorderSide(color: layoutColor!), // optional
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(10),
|
||
borderSide: BorderSide(color: layoutColor!), // optional
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.end,
|
||
children: [
|
||
TextButton(
|
||
onPressed: () {
|
||
Navigator.of(context).pop(); // Cancel
|
||
},
|
||
child: Text(
|
||
"Cancel",
|
||
style: GoogleFonts.poppins(fontSize: 12),
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
ElevatedButton(
|
||
onPressed: () {
|
||
// Use the entered text
|
||
String reason =
|
||
_excepntldescriptionController.text.trim();
|
||
if (reason.isNotEmpty) {
|
||
print("Reason entered: $reason");
|
||
setState(() {
|
||
hasExceptionalClassInUpdate = true;
|
||
validationErrors.remove("exceptional_plan_reason");
|
||
});
|
||
|
||
Navigator.of(context).pop(); // Close dialog
|
||
} else {
|
||
// Optional: show a validation message
|
||
}
|
||
},
|
||
child: Text(
|
||
"OK",
|
||
style: GoogleFonts.poppins(fontSize: 12),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildNonDescriptionColumn() {
|
||
return Column(
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Reason for travel policy exception*", // Your label
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
|
||
CustomTextFieldWrapper(
|
||
isFocused: focusStates["excepntldescriptionFocused"] ?? false,
|
||
// isFocused: _isdescriptionFocused,
|
||
width:
|
||
widget.isDesktop
|
||
? MediaQuery.of(context).size.width * 0.42
|
||
: null,
|
||
isDesktop: widget.isDesktop,
|
||
hasError: validationErrors["exceptional_plan_reason"] != null,
|
||
child: SizedBox(
|
||
height: 55,
|
||
child: TextField(
|
||
focusNode: focusNodes["excepntldescriptionFocusNode"],
|
||
controller: _excepntldescriptionController,
|
||
maxLines: 2,
|
||
keyboardType: TextInputType.multiline,
|
||
onChanged: (value) {
|
||
validationErrors.remove("exceptional_plan_reason");
|
||
},
|
||
style: TextStyle(fontSize: 12),
|
||
enabled: !widget.isViewMode,
|
||
decoration: InputDecoration(
|
||
labelText: "Reason for exception ",
|
||
labelStyle: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.grey,
|
||
),
|
||
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||
border: InputBorder.none,
|
||
|
||
contentPadding: EdgeInsets.symmetric(vertical: 1),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
|
||
if (validationErrors["exceptional_plan_reason"] != null)
|
||
Padding(
|
||
padding: EdgeInsets.only(top: 4),
|
||
child: Text(
|
||
validationErrors["exceptional_plan_reason"]!,
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.red,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildDescriptionColumn(isDesktop) {
|
||
return Column(
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Description", // Your label
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
|
||
// CustomTextFieldWrapper(
|
||
// isFocused: false,
|
||
// // isFocused: _isdescriptionFocused,
|
||
// isDesktop: widget.isDesktop,
|
||
// width: isDesktop
|
||
// ? MediaQuery.of(context).size.width * 0.4
|
||
// : MediaQuery.of(context).size.width * 0.85,
|
||
// child: SizedBox(
|
||
// height: 35,
|
||
// child: TextField(
|
||
// // focusNode: _descriptionFocusNode,
|
||
// controller: _descriptionController,
|
||
// style: TextStyle(fontSize: 12),
|
||
// enabled: !widget.isViewMode,
|
||
// decoration: InputDecoration(
|
||
// labelText: "Description",
|
||
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||
// border: InputBorder.none,
|
||
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// ),
|
||
CustomTextFieldWrapper(
|
||
isFocused: focusStates["descriptionFocused"] ?? false,
|
||
// isFocused: _isdescriptionFocused,
|
||
width:
|
||
widget.isDesktop
|
||
? MediaQuery.of(context).size.width * 0.32
|
||
: MediaQuery.of(context).size.width * 0.88,
|
||
isDesktop: widget.isDesktop,
|
||
child: SizedBox(
|
||
height: 55,
|
||
child: TextField(
|
||
focusNode: focusNodes["descriptionFocusNode"],
|
||
controller: _descriptionController,
|
||
maxLines: 2,
|
||
keyboardType: TextInputType.multiline,
|
||
style: TextStyle(fontSize: 12),
|
||
enabled: !widget.isViewMode,
|
||
decoration: InputDecoration(
|
||
labelText: "Description",
|
||
labelStyle: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.grey,
|
||
),
|
||
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||
border: InputBorder.none,
|
||
contentPadding: EdgeInsets.symmetric(vertical: 1),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
List<Widget> _buildSubmit1(isDesktop) {
|
||
return [
|
||
ElevatedButton(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: Colors.white,
|
||
foregroundColor: Colors.blueAccent,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
side: BorderSide(color: Colors.blueAccent, width: 2),
|
||
),
|
||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||
),
|
||
onPressed: () {
|
||
widget.isApprover
|
||
? context.go('/approvallist')
|
||
: context.go('/listPlan');
|
||
},
|
||
child: Text("Cancel"),
|
||
),
|
||
SizedBox(width: 20),
|
||
MouseRegion(
|
||
cursor:
|
||
widget.isViewMode
|
||
? SystemMouseCursors.forbidden
|
||
: SystemMouseCursors.click,
|
||
child: ElevatedButton(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor:
|
||
widget.isViewMode
|
||
? Colors.blueAccent
|
||
: Colors.blueAccent, // Keep original color
|
||
foregroundColor:
|
||
widget.isViewMode
|
||
? Colors.white
|
||
: Colors.white, // Keep original color
|
||
disabledBackgroundColor:
|
||
Colors.blueAccent, // Ensure color remains when disabled
|
||
disabledForegroundColor: Colors.white,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
side: BorderSide(color: Colors.blueAccent, width: 2),
|
||
),
|
||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||
),
|
||
onPressed:
|
||
widget.isViewMode
|
||
? null
|
||
: handleSubmit, // Disable when in view mode
|
||
child: Text("Submit"),
|
||
),
|
||
),
|
||
];
|
||
}
|
||
|
||
Widget _buildTN(bool isDesktop) {
|
||
return SizedBox(
|
||
width: isDesktop ? MediaQuery.of(context).size.width * 0.7443333 : 180,
|
||
height: 35,
|
||
child: TextField(
|
||
// focusNode: _tripTitleFocusNode,
|
||
controller: _tripTitleController,
|
||
// style: TextStyle(fontSize: 12),
|
||
style: GoogleFonts.poppins(
|
||
fontSize: isDesktop ? 16 : 14,
|
||
fontWeight: FontWeight.w600,
|
||
color: Colors.black,
|
||
),
|
||
enabled: !widget.isViewMode,
|
||
decoration: InputDecoration(
|
||
labelText: "Trip Name *",
|
||
labelStyle: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w400,
|
||
color: Colors.grey,
|
||
// color: Color(0xFF575A74),
|
||
),
|
||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||
border: InputBorder.none,
|
||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||
errorText: validationErrors["trip_title"], // <--- add this line
|
||
errorStyle: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildTripName(bool isDesktop) {
|
||
return SizedBox(
|
||
width: isDesktop ? MediaQuery.of(context).size.width * 0.5 : 180,
|
||
height: 35,
|
||
child: SingleChildScrollView(
|
||
scrollDirection: Axis.horizontal,
|
||
reverse: false, // show right-end on overflow
|
||
physics: BouncingScrollPhysics(),
|
||
child: ConstrainedBox(
|
||
constraints: BoxConstraints(
|
||
minWidth: isDesktop ? MediaQuery.of(context).size.width * 0.5 : 180,
|
||
),
|
||
child: IntrinsicWidth(
|
||
child: TextField(
|
||
controller: _tripTitleController,
|
||
maxLines: 1,
|
||
style: GoogleFonts.poppins(
|
||
fontSize: isDesktop ? 16 : 14,
|
||
fontWeight: FontWeight.w600,
|
||
color: Colors.black,
|
||
),
|
||
enabled: !widget.isViewMode,
|
||
decoration: InputDecoration(
|
||
labelText: "Trip Name *",
|
||
labelStyle: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w400,
|
||
color: Colors.grey,
|
||
),
|
||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||
border: InputBorder.none,
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 1,
|
||
vertical: 16,
|
||
),
|
||
errorText: validationErrors["trip_title"],
|
||
errorStyle: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.red,
|
||
),
|
||
),
|
||
keyboardType: TextInputType.text,
|
||
scrollPhysics: BouncingScrollPhysics(),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildApprovalItem(String title, String name) {
|
||
return Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"$title : ",
|
||
style: TextStyle(
|
||
fontFamily: "Archivo",
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w500,
|
||
color: Colors.black87,
|
||
),
|
||
),
|
||
Expanded(
|
||
child: Text(
|
||
name,
|
||
style: TextStyle(
|
||
fontFamily: "Archivo",
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.bold,
|
||
color: Colors.black87,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Color getStatusColor(String status) {
|
||
if (status == "Partially Approved") return Colors.yellow;
|
||
if (status == "Approved") return Colors.green;
|
||
if (status == "Completed") return Colors.green;
|
||
if (status == "Rejected") return Colors.red;
|
||
return Colors.grey;
|
||
}
|
||
|
||
List<Widget> _buildApproverControls(bool isDesktop) {
|
||
final statusText =
|
||
isApproverApproved
|
||
? "Approved"
|
||
: isApproverRejected
|
||
? "Rejected"
|
||
: (statusValue ?? "");
|
||
|
||
bool hasApprovals = planStatusList.any(
|
||
(item) => item.entries.any(
|
||
(entry) =>
|
||
entry.key.contains('status') &&
|
||
entry.value != null &&
|
||
entry.value.toString().isNotEmpty,
|
||
),
|
||
);
|
||
|
||
return [
|
||
// Row(
|
||
// mainAxisSize: MainAxisSize.min,
|
||
// children: [
|
||
// Text(
|
||
// isApproverApproved
|
||
// ? "Approved"
|
||
// : isApproverRejected
|
||
// ? "Rejected"
|
||
// : (statusValue ?? ""),
|
||
// style: TextStyle(
|
||
// fontFamily: "Archivo",
|
||
// fontWeight: FontWeight.bold,
|
||
// color: widget.layoutColor ?? Colors.grey,
|
||
// ),
|
||
// ),
|
||
// SizedBox(width: 5),
|
||
// MouseRegion(
|
||
// onEnter: (_) => setState(() => isStatusExpanded = true),
|
||
// onExit: (_) => setState(() => isStatusExpanded = false),
|
||
// child: Icon(
|
||
// Icons.approval_outlined,
|
||
// size: 18,
|
||
// color: isStatusExpanded ? Colors.green : Colors.grey,
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// ),
|
||
const SizedBox(height: 10, width: 10),
|
||
if (widget.isApprover && (widget.approverStatus == "Approval pending"))
|
||
GestureDetector(
|
||
// onTap: () {
|
||
// setState(() {
|
||
// isShowApprovalAction = !isShowApprovalAction;
|
||
//
|
||
// });
|
||
// },
|
||
onTap: () async {
|
||
final result = await showApprovalDialog(
|
||
context,
|
||
widget.layoutColor ?? Colors.grey,
|
||
);
|
||
|
||
if (result == 'approved') {
|
||
// User approved
|
||
setState(() {
|
||
isApproverApproved = true;
|
||
isApproverRejected = false;
|
||
statusValue = "Approved";
|
||
});
|
||
callApproveAPI(selectedPlanId!, planData['user_id']);
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (context) => const SavingLoader(),
|
||
);
|
||
} else if (result is String) {
|
||
// User rejected with remarks
|
||
_remarksController.text = result;
|
||
setState(() {
|
||
isApproverApproved = false;
|
||
isApproverRejected = true;
|
||
statusValue = "Rejected";
|
||
});
|
||
callRejectAPI(selectedPlanId!, planData['user_id'], result);
|
||
}
|
||
},
|
||
|
||
child: Icon(
|
||
Icons.edit,
|
||
size: 18,
|
||
color: isShowApprovalAction ? Colors.green : Colors.grey,
|
||
),
|
||
),
|
||
// const SizedBox(height: 10, width: 4),
|
||
// if (isShowApprovalAction)
|
||
// Row(
|
||
// mainAxisSize: MainAxisSize.min,
|
||
// children: [
|
||
// MouseRegion(
|
||
// cursor: widget.isViewMode
|
||
// ? SystemMouseCursors.forbidden
|
||
// : SystemMouseCursors.click,
|
||
// child: ElevatedButton(
|
||
// style: ElevatedButton.styleFrom(
|
||
// disabledBackgroundColor:
|
||
// statusValue == "Approved" ? Colors.green.shade100 : null,
|
||
// disabledForegroundColor:
|
||
// statusValue == "Approved" ? Colors.white : Colors.black,
|
||
// backgroundColor: isApproverApproved ||
|
||
// (!isApproverApproved &&
|
||
// !isApproverRejected &&
|
||
// statusValue == "Approved")
|
||
// ? Colors.green
|
||
// : Colors.grey.shade100,
|
||
// foregroundColor: isApproverApproved ||
|
||
// (!isApproverApproved &&
|
||
// !isApproverRejected &&
|
||
// statusValue == "Approved")
|
||
// ? Colors.white
|
||
// : Colors.black,
|
||
// shape: RoundedRectangleBorder(
|
||
// borderRadius: BorderRadius.circular(8),
|
||
// ),
|
||
// padding: EdgeInsets.symmetric(horizontal: 18, vertical: 10),
|
||
// ),
|
||
// onPressed: widget.isViewMode
|
||
// ? null
|
||
// : () async {
|
||
// final confirmed = await showApproveDialog(
|
||
// context, widget.layoutColor ?? Colors.grey);
|
||
// if (confirmed == true) {
|
||
// setState(() {
|
||
// isApproverApproved = true;
|
||
// isApproverRejected = false;
|
||
// statusValue = "Approved";
|
||
// });
|
||
// callApproveAPI(
|
||
// selectedPlanId!,
|
||
// planData['user_id'],
|
||
// );
|
||
// }
|
||
// },
|
||
// child: Text(
|
||
// isApproverApproved || statusValue == "Approved"
|
||
// ? "Approved"
|
||
// : "Approve",
|
||
// style: TextStyle(fontSize: 12),
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// const SizedBox(width: 10),
|
||
// MouseRegion(
|
||
// cursor: widget.isViewMode
|
||
// ? SystemMouseCursors.forbidden
|
||
// : SystemMouseCursors.click,
|
||
// child: ElevatedButton(
|
||
// style: ElevatedButton.styleFrom(
|
||
// disabledBackgroundColor: statusValue == "Rejected"
|
||
// ? Colors.redAccent.shade100
|
||
// : null,
|
||
// disabledForegroundColor:
|
||
// statusValue == "Rejected" ? Colors.white : Colors.black,
|
||
// backgroundColor: isApproverRejected ||
|
||
// (!isApproverApproved &&
|
||
// !isApproverRejected &&
|
||
// statusValue == "Rejected")
|
||
// ? Colors.redAccent
|
||
// : Colors.grey.shade100,
|
||
// foregroundColor: isApproverRejected ||
|
||
// (!isApproverApproved &&
|
||
// !isApproverRejected &&
|
||
// statusValue == "Rejected")
|
||
// ? Colors.white
|
||
// : Colors.black,
|
||
// shape: RoundedRectangleBorder(
|
||
// borderRadius: BorderRadius.circular(8),
|
||
// ),
|
||
// padding: EdgeInsets.symmetric(horizontal: 18, vertical: 10),
|
||
// ),
|
||
// onPressed: widget.isViewMode
|
||
// ? null
|
||
// : () async {
|
||
// final remarks = await showRejectDialog(
|
||
// context, widget.layoutColor ?? Colors.grey);
|
||
// if (remarks != null) {
|
||
// _remarksController.text = remarks;
|
||
// setState(() {
|
||
// isApproverApproved = false;
|
||
// isApproverRejected = true;
|
||
// statusValue = "Rejected";
|
||
// });
|
||
// callRejectAPI(
|
||
// selectedPlanId!, planData['user_id'], remarks);
|
||
// }
|
||
// },
|
||
// child: Text(
|
||
// isApproverRejected || statusValue == "Rejected"
|
||
// ? "Rejected"
|
||
// : "Reject",
|
||
// style: TextStyle(fontSize: 12),
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// ),
|
||
const SizedBox(height: 10, width: 10),
|
||
|
||
Stack(
|
||
clipBehavior: Clip.none, // allow tooltip to overflow
|
||
children: [
|
||
if (statusValue != "")
|
||
MouseRegion(
|
||
onEnter: (_) => setState(() => isStatusExpanded = true),
|
||
onExit: (_) => setState(() => isStatusExpanded = false),
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
border: Border.all(color: getStatusColor(statusText)),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.only(
|
||
top: 6.0,
|
||
bottom: 6.0,
|
||
left: 15,
|
||
right: 15,
|
||
),
|
||
child: Text(
|
||
statusText,
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w400,
|
||
color: getStatusColor(
|
||
isApproverApproved
|
||
? "Approved"
|
||
: isApproverRejected
|
||
? "Rejected"
|
||
: (statusValue ?? ""),
|
||
),
|
||
),
|
||
// style: TextStyle(
|
||
// fontFamily: "Roboto",
|
||
// fontWeight: FontWeight.w400,
|
||
// fontSize: 12,
|
||
//
|
||
// ),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
|
||
// Tooltip
|
||
if (isStatusExpanded)
|
||
Positioned(
|
||
right: 0,
|
||
top: 45, // Ensure the tooltip is above the container
|
||
// left:
|
||
// MediaQuery.of(context).size.width * 0.06, // align with label
|
||
child: Material(
|
||
// important: avoid clipping, give elevation
|
||
elevation: 4,
|
||
borderRadius: BorderRadius.circular(8),
|
||
child: SizedBox(
|
||
child: Container(
|
||
width:
|
||
isDesktop
|
||
? MediaQuery.of(context).size.width * 0.25
|
||
: MediaQuery.of(context).size.width,
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
// color: Colors.transparent,
|
||
border: Border.all(color: Colors.grey.shade300),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
if (!hasApprovals)
|
||
Center(
|
||
child: Text(
|
||
"--- No Approvals ---",
|
||
style: TextStyle(
|
||
fontFamily: "Archivo",
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w500,
|
||
color: Colors.black87,
|
||
),
|
||
),
|
||
),
|
||
for (int i = 0; i < planStatusList.length; i++) ...[
|
||
if (planStatusList[i].entries.any(
|
||
(entry) =>
|
||
entry.key.contains('status') &&
|
||
entry.value != null &&
|
||
entry.value.toString().isNotEmpty,
|
||
)) ...[
|
||
_buildApprovalItem(
|
||
"Approver ${i + 1}",
|
||
planStatusList[i].entries
|
||
.firstWhere(
|
||
(entry) => entry.key.contains('status'),
|
||
orElse: () => MapEntry('', ''),
|
||
)
|
||
.value
|
||
.toString(),
|
||
),
|
||
SizedBox(height: 6),
|
||
],
|
||
],
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
|
||
// if (isStatusExpanded)
|
||
// Positioned(
|
||
// top: 20, // adjust how much above you want
|
||
// left: 100,
|
||
// child: Container(
|
||
// margin: isDesktop
|
||
// ? const EdgeInsets.only(left: 0, top: 0)
|
||
// : const EdgeInsets.only(left: 5, top: 2),
|
||
// padding: const EdgeInsets.all(12),
|
||
// width: isDesktop
|
||
// ? MediaQuery.of(context).size.width * 0.2
|
||
// : MediaQuery.of(context).size.width,
|
||
// decoration: BoxDecoration(
|
||
// // color: Color(0xFFF5F5F5),
|
||
// color: Colors.white,
|
||
// border: Border.all(color: Colors.white, width: 0.2),
|
||
// borderRadius: BorderRadius.circular(8),
|
||
// ),
|
||
// child: Column(
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
// children: [
|
||
// if (!hasApprovals)
|
||
// Center(
|
||
// child: Text(
|
||
// "--- No Approvals ---",
|
||
// style: TextStyle(
|
||
// fontFamily: "Archivo",
|
||
// fontSize: 11,
|
||
// fontWeight: FontWeight.w500,
|
||
// color: Colors.black87,
|
||
// ),
|
||
// )),
|
||
// for (int i = 0; i < planStatusList.length; i++) ...[
|
||
// if (planStatusList[i].entries.any((entry) =>
|
||
// entry.key.contains('status') &&
|
||
// entry.value != null &&
|
||
// entry.value.toString().isNotEmpty)) ...[
|
||
// _buildApprovalItem(
|
||
// "Approver ${i + 1}",
|
||
// planStatusList[i]
|
||
// .entries
|
||
// .firstWhere(
|
||
// (entry) => entry.key.contains('status'),
|
||
// orElse: () => MapEntry('', ''),
|
||
// )
|
||
// .value
|
||
// .toString(),
|
||
// ),
|
||
// SizedBox(height: 6),
|
||
// ],
|
||
// ],
|
||
// ],
|
||
// ),
|
||
// ),
|
||
// ),
|
||
],
|
||
),
|
||
const SizedBox(height: 10, width: 10),
|
||
];
|
||
}
|
||
|
||
List<Widget> _buildPlanPdf(isDesktop) {
|
||
return [
|
||
Column(
|
||
children: [
|
||
ElevatedButton(
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: Color(0xFF114D8B),
|
||
foregroundColor: Colors.white,
|
||
disabledBackgroundColor: Color(0xFF114D8B),
|
||
disabledForegroundColor: Colors.white,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
side: BorderSide(color: Color(0xFF114D8B), width: 2),
|
||
),
|
||
padding:
|
||
isDesktop
|
||
? EdgeInsets.only(
|
||
top: 6.0,
|
||
bottom: 6.0,
|
||
left: 15,
|
||
right: 15,
|
||
)
|
||
: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||
),
|
||
onPressed: () {
|
||
getPdfDownload();
|
||
},
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min, // Ensures content fits nicely
|
||
children: [
|
||
if (isDesktop)
|
||
Text(
|
||
"Download PDF",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: isDesktop ? 13 : 11,
|
||
// fontWeight:
|
||
// FontWeight.w500,
|
||
),
|
||
// style: TextStyle(fontSize: isDesktop ? 13 : 11),
|
||
),
|
||
if (isDesktop)
|
||
SizedBox(width: 8), // spacing between icon and text
|
||
Icon(Icons.download_rounded, size: 15, color: Colors.white),
|
||
],
|
||
),
|
||
),
|
||
|
||
// InkWell(
|
||
// hoverColor: Colors.white,
|
||
// onTap: () {
|
||
// print('📄 PDF icon clicked!');
|
||
// getPdfDownload();
|
||
// },
|
||
// child: Row(
|
||
// children: [
|
||
// Text("Download PDF"),
|
||
// SizedBox(
|
||
// width: 10,
|
||
// ),
|
||
// Transform.scale(
|
||
// scale: 1.5, // 1.0 = normal, 1.5 = 50% bigger
|
||
// child: Image.asset(
|
||
// 'assets/images/IconsImg/planPdf_icon.png',
|
||
// width: 25,
|
||
// height: 25, // keep the real height small
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// )),
|
||
// SizedBox(height: 10), // small spacing
|
||
],
|
||
),
|
||
];
|
||
}
|
||
|
||
void _showInputDialog(String title) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (BuildContext context) {
|
||
return UserSelectionDialog(
|
||
title: title,
|
||
onSubmit: (input, userId, isTraveller) {
|
||
setState(() {
|
||
otherUserName = input;
|
||
selectedplanUserId = userId;
|
||
selectedIstravelUser = isTraveller;
|
||
});
|
||
print("USer entered : $otherUserName $userId $isTraveller");
|
||
getSelectedPlanFor();
|
||
},
|
||
onClose: () {
|
||
print("Choosede Clsoes");
|
||
fetchUserDetails();
|
||
},
|
||
layoutColorForUser: widget.layoutColor!,
|
||
currentUser: selfId,
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|