latest commit 06-06-2025

This commit is contained in:
vadivelJ96 2025-06-06 11:51:45 +05:30
commit ef79391915
117 changed files with 23708 additions and 16813 deletions

View File

@ -5,15 +5,7 @@
android:label="frontend" android:label="frontend"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
@ -36,6 +28,15 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data <meta-data

View File

@ -4,4 +4,5 @@
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

View File

@ -13,6 +13,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../../routes/custom_appBar.dart'; import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart'; import '../../routes/custom_drawer.dart';
import '../../routes/mainLayout.dart';
import '../../services/apiService.dart'; import '../../services/apiService.dart';
import '../../utils/auth_utils.dart'; import '../../utils/auth_utils.dart';
import '../../utils/pagination.dart'; import '../../utils/pagination.dart';
@ -62,7 +63,6 @@ class _ListAllPlansState extends State<ListAllPlans> {
// }); // });
// }); // });
}); });
// futurePlans = fetchPlans(); // futurePlans = fetchPlans();
} }
@ -70,16 +70,20 @@ class _ListAllPlansState extends State<ListAllPlans> {
print("allPlans before filtering: $allPlans"); print("allPlans before filtering: $allPlans");
final lowerQuery = query.toLowerCase(); final lowerQuery = query.toLowerCase();
setState(() { setState(() {
filteredPlans = allPlans.where((plan) { filteredPlans =
allPlans.where((plan) {
return (plan.planId?.toLowerCase().contains(lowerQuery) ?? false) || return (plan.planId?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.employeeCode?.toLowerCase().contains(lowerQuery) ?? false) || (plan.employeeCode?.toLowerCase().contains(lowerQuery) ??
false) ||
(plan.tripTitle?.toLowerCase().contains(lowerQuery) ?? false) || (plan.tripTitle?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.userName?.toLowerCase().contains(lowerQuery) ?? false) || (plan.userName?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.travellerName?.toLowerCase().contains(lowerQuery) ?? false) || (plan.travellerName?.toLowerCase().contains(lowerQuery) ??
false) ||
(plan.tripType?.toLowerCase().contains(lowerQuery) ?? false) || (plan.tripType?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.createdOn?.toLowerCase().contains(lowerQuery) ?? false) || (plan.createdOn?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false); (plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false);
}).toList(); }).toList();
currentPage = 0;
}); });
print("filteredPlans: $filteredPlans"); print("filteredPlans: $filteredPlans");
} }
@ -89,11 +93,13 @@ class _ListAllPlansState extends State<ListAllPlans> {
String? bodyStringColor = await getBodyColor(); String? bodyStringColor = await getBodyColor();
setState(() { setState(() {
layoutColor = layoutString != null layoutColor =
layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyStringColor != null bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
@ -202,6 +208,7 @@ class _ListAllPlansState extends State<ListAllPlans> {
if (response.statusCode == 200) { if (response.statusCode == 200) {
final data = json.decode(response.body); final data = json.decode(response.body);
// List<dynamic> plansJson = [];
List<dynamic> plansJson = data['data']; List<dynamic> plansJson = data['data'];
return plansJson.map((json) => Plan.fromJson(json)).toList(); return plansJson.map((json) => Plan.fromJson(json)).toList();
} else { } else {
@ -246,8 +253,10 @@ class _ListAllPlansState extends State<ListAllPlans> {
} }
void deletePlan(String planId) async { void deletePlan(String planId) async {
bool confirmed = bool confirmed = await apiService.showCancelConfirmationDialog(
await apiService.showCancelConfirmationDialog(context, layoutColor); context,
layoutColor,
);
if (confirmed) { if (confirmed) {
try { try {
@ -264,8 +273,10 @@ class _ListAllPlansState extends State<ListAllPlans> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFf5f5f5), backgroundColor: Color(0xFFf5f5f5),
@ -276,23 +287,27 @@ class _ListAllPlansState extends State<ListAllPlans> {
appBar: CustomAppBar(isDesktop: isDesktop), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding:
isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height * vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
child: Row( child: Row(
children: [ children: [
// if (isDesktop) CustomDrawer(isDesktop: true), // if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildGroupListLayout(isDesktop)) Expanded(child: buildGroupListLayout(isDesktop)),
], ],
), ),
), ),
); );
}); },
);
} }
Widget buildGroupListLayout(bool isDesktop) { Widget buildGroupListLayout(bool isDesktop) {
@ -323,8 +338,9 @@ class _ListAllPlansState extends State<ListAllPlans> {
String _formatDate(String rawDate) { String _formatDate(String rawDate) {
try { try {
final dateTime = DateTime.parse(rawDate); final dateTime = DateTime.parse(rawDate);
return DateFormat('dd, MMM yyyy HH:mm') return DateFormat(
.format(dateTime); // 24-hour format 'dd, MMM yyyy HH:mm',
).format(dateTime); // 24-hour format
} catch (e) { } catch (e) {
return rawDate; // fallback if parsing fails return rawDate; // fallback if parsing fails
} }
@ -333,11 +349,13 @@ class _ListAllPlansState extends State<ListAllPlans> {
return Container( return Container(
margin: isDesktop ? EdgeInsets.all(10.0) : null, margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20), padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
height: isDesktop height:
isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
decoration: BoxDecoration( decoration: BoxDecoration(
border: isDesktop border:
isDesktop
? Border.all( ? Border.all(
width: 2, width: 2,
color: Colors.white, color: Colors.white,
@ -383,9 +401,7 @@ class _ListAllPlansState extends State<ListAllPlans> {
], ],
), ),
SizedBox( SizedBox(width: 1),
width: 1,
),
Spacer(), Spacer(),
if (isDesktop) if (isDesktop)
Container( Container(
@ -396,8 +412,10 @@ class _ListAllPlansState extends State<ListAllPlans> {
onChanged: filterPlans, onChanged: filterPlans,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search...", hintText: "Search...",
hintStyle: hintStyle: TextStyle(
TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -409,22 +427,23 @@ class _ListAllPlansState extends State<ListAllPlans> {
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, width: 0.5), color: Colors.grey.shade200,
width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
// SizedBox(width: 16), // SizedBox(width: 16),
Spacer(), Spacer(),
// ElevatedButton( // ElevatedButton(
// style: ElevatedButton.styleFrom( // style: ElevatedButton.styleFrom(
@ -522,8 +541,10 @@ class _ListAllPlansState extends State<ListAllPlans> {
onChanged: filterPlans, onChanged: filterPlans,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search...", hintText: "Search...",
hintStyle: hintStyle: TextStyle(
TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -535,17 +556,19 @@ class _ListAllPlansState extends State<ListAllPlans> {
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, width: 0.5), color: Colors.grey.shade200,
width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
], ],
@ -558,6 +581,8 @@ class _ListAllPlansState extends State<ListAllPlans> {
} else if (snapshot.hasError || } else if (snapshot.hasError ||
!snapshot.hasData || !snapshot.hasData ||
snapshot.data!.isEmpty) { snapshot.data!.isEmpty) {
final adjHgt = MediaQuery.of(context).size.height;
return Center( return Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
@ -575,21 +600,15 @@ class _ListAllPlansState extends State<ListAllPlans> {
// color: Colors.redAccent), // color: Colors.redAccent),
// //
// ), // ),
const SizedBox(height: 15), SizedBox(height: adjHgt / 4),
Text( Text(
"No Trips", " No Trips Found",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w500,
color: Colors.black54), color: Colors.black54,
), ),
const SizedBox(height: 10),
Text(
"Please Create Trip",
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 12, color: Colors.grey),
), ),
], ],
), ),
@ -605,10 +624,13 @@ class _ListAllPlansState extends State<ListAllPlans> {
List<Plan> plans = List<Plan> plans =
searchController.text.isEmpty ? allPlans : filteredPlans; searchController.text.isEmpty ? allPlans : filteredPlans;
plans.sort((a, b) => plans.sort(
int.parse(b.planId).compareTo(int.parse(a.planId))); (a, b) =>
int.parse(b.planId).compareTo(int.parse(a.planId)),
);
List<Plan> paginatedPlans = plans List<Plan> paginatedPlans =
plans
.skip(currentPage * itemsPerPage) .skip(currentPage * itemsPerPage)
.take(itemsPerPage) .take(itemsPerPage)
.toList(); .toList();
@ -624,112 +646,169 @@ class _ListAllPlansState extends State<ListAllPlans> {
columnSpacing: isDesktop ? 24.0 : 16.0, columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder( border: TableBorder(
horizontalInside: BorderSide( horizontalInside: BorderSide(
width: 0.5, color: Colors.grey.shade200), width: 0.5,
color: Colors.grey.shade200,
),
), ),
columns: [ columns: [
DataColumn( DataColumn(
label: Text( label: Text(
'Trip ID', 'Trip ID',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Trip Name', 'Trip Name',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Emp Code', 'Emp Code',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Traveller', 'Traveller',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Trip Type', 'Trip Type',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Created On', 'Created On',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Status', 'Status',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Actions', 'Actions',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
], ],
rows: paginatedPlans.map((plan) { rows:
return DataRow(cells: [ paginatedPlans.map((plan) {
DataCell(Text(plan.planId, return DataRow(
cells: [
DataCell(
Text(
plan.planId,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text(plan.tripTitle, ),
),
DataCell(
Text(
plan.tripTitle,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis,
DataCell(Text(plan.employeeCode ?? " - ", ),
),
DataCell(
Text(
plan.employeeCode ?? " - ",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text( ),
),
DataCell(
Text(
plan.userName.isNotEmpty plan.userName.isNotEmpty
? plan.userName ? plan.userName
: plan.travellerName, : plan.travellerName,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text(plan.tripType, ),
),
DataCell(
Text(
plan.tripType,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text(_formatDate(plan.createdOn), ),
),
DataCell(
Text(
_formatDate(plan.createdOn),
// plan.createdOn, // plan.createdOn,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
),
),
DataCell( DataCell(
Container( Container(
width: double width:
double
.infinity, // Set your desired fixed size (equal width and height) .infinity, // Set your desired fixed size (equal width and height)
height: 25, height: 25,
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: getStatusColor(plan.statusValue), color: getStatusColor(
borderRadius: BorderRadius.circular(10), plan.statusValue,
),
borderRadius: BorderRadius.circular(
10,
),
), ),
child: Text( child: Text(
plan.statusValue, plan.statusValue,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: color: getStatusTextColor(
getStatusTextColor(plan.statusValue), plan.statusValue,
),
fontSize: 12, fontSize: 12,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -749,80 +828,132 @@ class _ListAllPlansState extends State<ListAllPlans> {
color: Color(0xFF475569), color: Color(0xFF475569),
size: 14, size: 14,
), ),
itemBuilder: (context) => [ itemBuilder:
(context) => [
CustomPopupMenuEntry( CustomPopupMenuEntry(
child: Container( child: Container(
padding: EdgeInsets.symmetric( padding:
horizontal: 8, vertical: 8), EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize:
MainAxisSize.min,
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.center, MainAxisAlignment
.center,
children: [ children: [
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.remove_red_eye, Icons
color: Color(0xFF475569), .remove_red_eye,
size: 18), color: Color(
0xFF475569,
),
size: 18,
),
tooltip:
'View The Trip Details',
onPressed: () { onPressed: () {
Navigator.pop( Navigator.pop(
context); // Close popup manually context,
); // Close popup manually
ApiService.viewPlan( ApiService.viewPlan(
context, plan.planId, context,
isViewMode: true); plan.planId,
isViewMode:
true,
);
}, },
), ),
IconButton( IconButton(
icon: Image.asset( icon: Image.asset(
'assets/images/IconsImg/edit.png', 'assets/images/IconsImg/edit.png',
width: 20, width: 20,
height: 15), height: 15,
),
tooltip:
'Edit The Trip Details',
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(
context,
);
ApiService.viewPlan( ApiService.viewPlan(
context, plan.planId, context,
isViewMode: false); plan.planId,
isViewMode:
false,
);
}, },
), ),
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.cancel_rounded, Icons
size: 18), .cancel_rounded,
size: 18,
),
tooltip:
'Cancellation The Trip Details',
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(
deletePlan(plan.planId); context,
);
deletePlan(
plan.planId,
);
}, },
), ),
IconButton( IconButton(
icon: Icon(Icons.download, icon: Icon(
color: Color(0xFF114D8B), Icons.download,
size: 18), color: Color(
0xFF114D8B,
),
size: 18,
),
tooltip:
'Download The Trip Details',
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(
apiService.getPdfDownload( context,
plan.planId); );
apiService
.getPdfDownload(
plan.planId,
);
}, },
), ),
IconButton( IconButton(
icon: const Icon( icon: const Icon(
Icons.comment, Icons.comment,
color: Color(0xFF475569), color: Color(
0xFF475569,
),
size: 11, size: 11,
), ),
tooltip:
'Trip Comments',
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, context:
builder: (context) => context,
CommentModalList( builder:
(
context,
) => CommentModalList(
// planId: plan.planId, // planId: plan.planId,
planId: plan planId:
.planId plan.planId
.toString(), .toString(),
layoutColorForUser: layoutColorForUser:
layoutColor!, layoutColor!,
role: "Admin"), role:
"Admin",
),
); );
}), },
),
], ],
), ),
), ),
@ -832,7 +963,8 @@ class _ListAllPlansState extends State<ListAllPlans> {
], ],
), ),
), ),
]); ],
);
}).toList(), }).toList(),
), ),
); );
@ -846,8 +978,10 @@ class _ListAllPlansState extends State<ListAllPlans> {
final plan = paginatedPlans[index]; final plan = paginatedPlans[index];
return Card( return Card(
color: Colors.white, color: Colors.white,
margin: margin: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 12, vertical: 6), horizontal: 12,
vertical: 6,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@ -872,18 +1006,23 @@ class _ListAllPlansState extends State<ListAllPlans> {
children: [ children: [
Container( Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 8, vertical: 4), horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: getStatusColor( color: getStatusColor(
plan.statusValue), plan.statusValue,
borderRadius: ),
BorderRadius.circular(8), borderRadius: BorderRadius.circular(
8,
),
), ),
child: Text( child: Text(
plan.statusValue, plan.statusValue,
style: TextStyle( style: TextStyle(
color: getStatusTextColor( color: getStatusTextColor(
plan.statusValue), plan.statusValue,
),
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@ -912,11 +1051,14 @@ class _ListAllPlansState extends State<ListAllPlans> {
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
children: [ children: [
Text(' ${plan.tripTitle}', Text(
' ${plan.tripTitle}',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold,
),
),
], ],
), ),
], ],
@ -931,24 +1073,28 @@ class _ListAllPlansState extends State<ListAllPlans> {
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
children: [ children: [
Text(' ${plan.tripType}', Text(
' ${plan.tripType}',
style: TextStyle( style: TextStyle(
fontSize: 9, fontSize: 9,
color: Colors.black87, color: Colors.black87,
fontFamily: "Inter", fontFamily: "Inter",
)), ),
),
], ],
), ),
Column( Column(
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
children: [ children: [
Text('${_formatDate(plan.createdOn)}', Text(
'${_formatDate(plan.createdOn)}',
style: TextStyle( style: TextStyle(
fontSize: 9, fontSize: 9,
color: Colors.black87, color: Colors.black87,
fontFamily: "Inter", fontFamily: "Inter",
)), ),
),
], ],
), ),
], ],
@ -968,7 +1114,8 @@ class _ListAllPlansState extends State<ListAllPlans> {
style: TextStyle( style: TextStyle(
fontSize: 9, fontSize: 9,
fontFamily: "Inter", fontFamily: "Inter",
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
@ -982,7 +1129,8 @@ class _ListAllPlansState extends State<ListAllPlans> {
style: TextStyle( style: TextStyle(
fontSize: 9, fontSize: 9,
fontFamily: "Inter", fontFamily: "Inter",
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
@ -1002,14 +1150,17 @@ class _ListAllPlansState extends State<ListAllPlans> {
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: isDesktop child:
isDesktop
? (searchController.text.isNotEmpty && ? (searchController.text.isNotEmpty &&
filteredPlans.isEmpty filteredPlans.isEmpty
? Center( ? Center(
child: Text( child: Text(
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey), fontSize: 14,
color: Colors.grey,
),
), ),
) )
: SingleChildScrollView( : SingleChildScrollView(
@ -1022,7 +1173,9 @@ class _ListAllPlansState extends State<ListAllPlans> {
child: Text( child: Text(
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey), fontSize: 14,
color: Colors.grey,
),
), ),
) )
: buildMobileCardView(paginatedPlans)), : buildMobileCardView(paginatedPlans)),
@ -1055,7 +1208,7 @@ class _ListAllPlansState extends State<ListAllPlans> {
), ),
); );
}, },
) ),
], ],
), ),
), ),

View File

@ -69,12 +69,15 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
print("allPlans before filtering: $allPlans"); print("allPlans before filtering: $allPlans");
final lowerQuery = query.toLowerCase(); final lowerQuery = query.toLowerCase();
setState(() { setState(() {
filteredPlans = allPlans.where((plan) { filteredPlans =
allPlans.where((plan) {
return (plan.planId?.toLowerCase().contains(lowerQuery) ?? false) || return (plan.planId?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.employeeCode?.toLowerCase().contains(lowerQuery) ?? false) || (plan.employeeCode?.toLowerCase().contains(lowerQuery) ??
false) ||
(plan.tripTitle?.toLowerCase().contains(lowerQuery) ?? false) || (plan.tripTitle?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.userName?.toLowerCase().contains(lowerQuery) ?? false) || (plan.userName?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.travellerName?.toLowerCase().contains(lowerQuery) ?? false) || (plan.travellerName?.toLowerCase().contains(lowerQuery) ??
false) ||
(plan.tripType?.toLowerCase().contains(lowerQuery) ?? false) || (plan.tripType?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.createdOn?.toLowerCase().contains(lowerQuery) ?? false) || (plan.createdOn?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false); (plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false);
@ -88,11 +91,13 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
String? bodyStringColor = await getBodyColor(); String? bodyStringColor = await getBodyColor();
setState(() { setState(() {
layoutColor = layoutString != null layoutColor =
layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyStringColor != null bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
@ -195,7 +200,8 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
if (response.statusCode == 200) { if (response.statusCode == 200) {
final data = json.decode(response.body); final data = json.decode(response.body);
List<dynamic> plansJson = data['data']; List<dynamic> plansJson = [];
// List<dynamic> plansJson = data['data'];
return plansJson.map((json) => Plan.fromJson(json)).toList(); return plansJson.map((json) => Plan.fromJson(json)).toList();
} else { } else {
throw Exception('Failed to load plans'); throw Exception('Failed to load plans');
@ -239,8 +245,10 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
} }
void deletePlan(String planId) async { void deletePlan(String planId) async {
bool confirmed = bool confirmed = await apiService.showCancelConfirmationDialog(
await apiService.showCancelConfirmationDialog(context, layoutColor); context,
layoutColor,
);
if (confirmed) { if (confirmed) {
try { try {
@ -257,8 +265,10 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFf5f5f5), backgroundColor: Color(0xFFf5f5f5),
@ -267,23 +277,27 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
appBar: CustomAppBar(isDesktop: isDesktop), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding:
isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height * vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
child: Row( child: Row(
children: [ children: [
// if (isDesktop) CustomDrawer(isDesktop: true), // if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildGroupListLayout(isDesktop)) Expanded(child: buildGroupListLayout(isDesktop)),
], ],
), ),
), ),
); );
}); },
);
} }
Widget buildGroupListLayout(bool isDesktop) { Widget buildGroupListLayout(bool isDesktop) {
@ -313,8 +327,9 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
String _formatDate(String rawDate) { String _formatDate(String rawDate) {
try { try {
final dateTime = DateTime.parse(rawDate); final dateTime = DateTime.parse(rawDate);
return DateFormat('dd, MMM yyyy HH:mm') return DateFormat(
.format(dateTime); // 24-hour format 'dd, MMM yyyy HH:mm',
).format(dateTime); // 24-hour format
} catch (e) { } catch (e) {
return rawDate; // fallback if parsing fails return rawDate; // fallback if parsing fails
} }
@ -323,11 +338,13 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
return Container( return Container(
margin: isDesktop ? EdgeInsets.all(10.0) : null, margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20), padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
height: isDesktop height:
isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
decoration: BoxDecoration( decoration: BoxDecoration(
border: isDesktop border:
isDesktop
? Border.all( ? Border.all(
width: 2, width: 2,
color: Colors.white, color: Colors.white,
@ -382,8 +399,10 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
onChanged: filterPlans, onChanged: filterPlans,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search...", hintText: "Search...",
hintStyle: hintStyle: TextStyle(
TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -395,23 +414,23 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, width: 0.5), color: Colors.grey.shade200,
width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: TextStyle(
fontSize: 12,
fontFamily: "Inter",
), ),
style: TextStyle(fontSize: 12, fontFamily: "Inter"),
), ),
), ),
// SizedBox(width: 16), // SizedBox(width: 16),
Spacer(), Spacer(),
// ElevatedButton( // ElevatedButton(
// style: ElevatedButton.styleFrom( // style: ElevatedButton.styleFrom(
@ -447,8 +466,10 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
onChanged: filterPlans, onChanged: filterPlans,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search...", hintText: "Search...",
hintStyle: hintStyle: TextStyle(
TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -460,17 +481,19 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, width: 0.5), color: Colors.grey.shade200,
width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
], ],
@ -479,6 +502,8 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
FutureBuilder<List<Plan>>( FutureBuilder<List<Plan>>(
future: futurePlans, future: futurePlans,
builder: (context, snapshot) { builder: (context, snapshot) {
final adjHgt = MediaQuery.of(context).size.height;
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError || } else if (snapshot.hasError ||
@ -490,7 +515,7 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
SizedBox(height: 20), // SizedBox(height: 20),
// Icon(Icons.error_outline, // Icon(Icons.error_outline,
// color: Colors.redAccent, size: 60), // color: Colors.redAccent, size: 60),
// SizedBox(height: 1), // SizedBox(height: 1),
@ -499,21 +524,23 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
// fontSize: 22, // fontSize: 22,
// fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
// color: Colors.redAccent)), // color: Colors.redAccent)),
SizedBox(height: 20),
isDesktop isDesktop
? SizedBox( ? SizedBox(
width: width:
MediaQuery.of(context).size.width * 5.5, MediaQuery.of(context).size.width * 5.5,
) )
: SizedBox.shrink(), : SizedBox.shrink(),
SizedBox(height: adjHgt / 4),
Text("No Trips Pending For Your Approvals", Text(
" No Trips Found",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: GoogleFonts.poppins(
fontSize: 20, fontSize: 20,
fontFamily: "Inter", fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600, color: Colors.black54,
color: Colors.black54)), ),
),
SizedBox(height: 10), SizedBox(height: 10),
// Text("Please Create Trip", // Text("Please Create Trip",
// textAlign: TextAlign.center, // textAlign: TextAlign.center,
@ -536,10 +563,13 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
List<Plan> plans = List<Plan> plans =
searchController.text.isEmpty ? allPlans : filteredPlans; searchController.text.isEmpty ? allPlans : filteredPlans;
plans.sort((a, b) => plans.sort(
int.parse(b.planId).compareTo(int.parse(a.planId))); (a, b) =>
int.parse(b.planId).compareTo(int.parse(a.planId)),
);
List<Plan> paginatedPlans = plans List<Plan> paginatedPlans =
plans
.skip(currentPage * itemsPerPage) .skip(currentPage * itemsPerPage)
.take(itemsPerPage) .take(itemsPerPage)
.toList(); .toList();
@ -555,117 +585,174 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
columnSpacing: isDesktop ? 24.0 : 16.0, columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder( border: TableBorder(
horizontalInside: BorderSide( horizontalInside: BorderSide(
width: 0.5, color: Colors.grey.shade200), width: 0.5,
color: Colors.grey.shade200,
),
), ),
columns: [ columns: [
DataColumn( DataColumn(
label: Text( label: Text(
'Trip ID', 'Trip ID',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Trip Name', 'Trip Name',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Emp Code', 'Emp Code',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Traveller', 'Traveller',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Trip Type', 'Trip Type',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Created On', 'Created On',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Status', 'Status',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Actions', 'Actions',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600), fontSize: 13,
)), fontWeight: FontWeight.w600,
),
),
),
], ],
rows: paginatedPlans.map((plan) { rows:
return DataRow(cells: [ paginatedPlans.map((plan) {
DataCell(Text(plan.planId, return DataRow(
cells: [
DataCell(
Text(
plan.planId,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
),
),
DataCell(Text(plan.tripTitle, DataCell(
Text(
plan.tripTitle,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis,
),
),
DataCell(Text(plan.employeeCode ?? " - ", DataCell(
Text(
plan.employeeCode ?? " - ",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text( ),
),
DataCell(
Text(
plan.userName.isNotEmpty plan.userName.isNotEmpty
? plan.userName ? plan.userName
: plan.travellerName, : plan.travellerName,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text(plan.tripType, ),
),
DataCell(
Text(
plan.tripType,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text(_formatDate(plan.createdOn), ),
),
DataCell(
Text(
_formatDate(plan.createdOn),
// plan.createdOn, // plan.createdOn,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
),
),
DataCell( DataCell(
Container( Container(
padding: const EdgeInsets.all(3), padding: const EdgeInsets.all(3),
// width: // width:
// 150, // Set your desired fixed size (equal width and height) // 150, // Set your desired fixed size (equal width and height)
width: double width:
double
.infinity, // Set your desired fixed size (equal width and height) .infinity, // Set your desired fixed size (equal width and height)
height: 25, height: 25,
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: getStatusColor(plan.statusValue), color: getStatusColor(
borderRadius: BorderRadius.circular(10), plan.statusValue,
),
borderRadius: BorderRadius.circular(
10,
),
), ),
child: Text( child: Text(
plan.statusValue, plan.statusValue,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: color: getStatusTextColor(
getStatusTextColor(plan.statusValue), plan.statusValue,
),
fontSize: 12, fontSize: 12,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -681,29 +768,45 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
color: Colors.white, color: Colors.white,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
offset: Offset(0, 30), offset: Offset(0, 30),
icon: Icon(Icons.more_vert, icon: Icon(
color: Color(0xFF475569)), Icons.more_vert,
itemBuilder: (context) => [ color: Color(0xFF475569),
),
itemBuilder:
(context) => [
CustomPopupMenuEntry( CustomPopupMenuEntry(
child: Container( child: Container(
padding: EdgeInsets.symmetric( padding:
horizontal: 8, vertical: 8), EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize:
MainAxisSize.min,
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.center, MainAxisAlignment
.center,
children: [ children: [
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.remove_red_eye, Icons
color: Color(0xFF475569), .remove_red_eye,
size: 16), color: Color(
0xFF475569,
),
size: 16,
),
onPressed: () { onPressed: () {
Navigator.pop( Navigator.pop(
context); // Close popup manually context,
); // Close popup manually
ApiService.viewPlan( ApiService.viewPlan(
context, plan.planId, context,
isViewMode: true); plan.planId,
isViewMode:
true,
);
}, },
), ),
// IconButton( // IconButton(
@ -720,44 +823,65 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
// ), // ),
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.cancel_rounded, Icons
size: 18), .cancel_rounded,
size: 18,
),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(
deletePlan(plan.planId); context,
);
deletePlan(
plan.planId,
);
}, },
), ),
IconButton( IconButton(
icon: Icon(Icons.download, icon: Icon(
color: Color(0xFF114D8B), Icons.download,
size: 18), color: Color(
0xFF114D8B,
),
size: 18,
),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(
apiService.getPdfDownload( context,
plan.planId); );
apiService
.getPdfDownload(
plan.planId,
);
}, },
), ),
IconButton( IconButton(
icon: const Icon( icon: const Icon(
Icons.comment, Icons.comment,
color: Color(0xFF475569), color: Color(
0xFF475569,
),
size: 11, size: 11,
), ),
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, context:
builder: (context) => context,
CommentModal( builder:
(
context,
) => CommentModal(
// planId: plan.planId, // planId: plan.planId,
planId: plan planId:
.planId plan.planId
.toString(), .toString(),
layoutColorForUser: layoutColorForUser:
layoutColor!, layoutColor!,
role: role:
"Travel Agent"), "Travel Agent",
),
); );
}), },
),
], ],
), ),
), ),
@ -807,7 +931,8 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
// apiService.getPdfDownload(plan.planId); // apiService.getPdfDownload(plan.planId);
// }), // }),
// ])), // ])),
]); ],
);
}).toList(), }).toList(),
), ),
); );
@ -821,8 +946,10 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
final plan = paginatedPlans[index]; final plan = paginatedPlans[index];
return Card( return Card(
color: Colors.white, color: Colors.white,
margin: margin: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 12, vertical: 6), horizontal: 12,
vertical: 6,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@ -839,7 +966,9 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
children: [ children: [
Container( Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 8, vertical: 4), horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: getStatusColor(plan.statusValue), color: getStatusColor(plan.statusValue),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@ -848,7 +977,8 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
plan.statusValue, plan.statusValue,
style: TextStyle( style: TextStyle(
color: getStatusTextColor( color: getStatusTextColor(
plan.statusValue), plan.statusValue,
),
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@ -871,11 +1001,14 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
children: [ children: [
Text(' ${plan.tripTitle}', Text(
' ${plan.tripTitle}',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold,
),
),
], ],
), ),
], ],
@ -890,24 +1023,28 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
children: [ children: [
Text(' ${plan.tripType}', Text(
' ${plan.tripType}',
style: TextStyle( style: TextStyle(
fontSize: 9, fontSize: 9,
color: Colors.black87, color: Colors.black87,
fontFamily: "Inter", fontFamily: "Inter",
)), ),
),
], ],
), ),
Column( Column(
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
children: [ children: [
Text('${_formatDate(plan.createdOn)}', Text(
'${_formatDate(plan.createdOn)}',
style: TextStyle( style: TextStyle(
fontSize: 9, fontSize: 9,
color: Colors.black87, color: Colors.black87,
fontFamily: "Inter", fontFamily: "Inter",
)), ),
),
], ],
), ),
], ],
@ -927,7 +1064,8 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
style: TextStyle( style: TextStyle(
fontSize: 9, fontSize: 9,
fontFamily: "Inter", fontFamily: "Inter",
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
@ -941,7 +1079,8 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
style: TextStyle( style: TextStyle(
fontSize: 9, fontSize: 9,
fontFamily: "Inter", fontFamily: "Inter",
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
@ -963,14 +1102,17 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: isDesktop child:
isDesktop
? (searchController.text.isNotEmpty && ? (searchController.text.isNotEmpty &&
filteredPlans.isEmpty filteredPlans.isEmpty
? Center( ? Center(
child: Text( child: Text(
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey), fontSize: 14,
color: Colors.grey,
),
), ),
) )
: SingleChildScrollView( : SingleChildScrollView(
@ -983,7 +1125,9 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
child: Text( child: Text(
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey), fontSize: 14,
color: Colors.grey,
),
), ),
) )
: buildMobileCardView(paginatedPlans)), : buildMobileCardView(paginatedPlans)),
@ -1016,7 +1160,7 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
), ),
); );
}, },
) ),
], ],
), ),
), ),

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
Future<dynamic> showApprovalDialog( Future<dynamic> showApprovalDialog(
BuildContext context, Color layoutColor) async { BuildContext context,
Color layoutColor,
) async {
String selectedAction = ""; // "", "accept", "reject" String selectedAction = ""; // "", "accept", "reject"
String remarks = ""; String remarks = "";
@ -24,13 +26,12 @@ Future<dynamic> showApprovalDialog(
Text( Text(
"To Approve or Reject Trip", "To Approve or Reject Trip",
style: TextStyle( style: TextStyle(
fontFamily: "Inter", fontWeight: FontWeight.w500), fontFamily: "Inter",
fontWeight: FontWeight.w500,
),
), ),
IconButton( IconButton(
icon: const Icon( icon: const Icon(Icons.close, size: 15),
Icons.close,
size: 15,
),
onPressed: () { onPressed: () {
Navigator.pop(context, null); // Close the dialog Navigator.pop(context, null); // Close the dialog
}, },
@ -45,10 +46,12 @@ Future<dynamic> showApprovalDialog(
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: selectedAction == "accept" backgroundColor:
? layoutColor selectedAction == "accept"
? Colors.green
: Colors.grey.shade200, : Colors.grey.shade200,
foregroundColor: selectedAction == "accept" foregroundColor:
selectedAction == "accept"
? Colors.white ? Colors.white
: Colors.black, : Colors.black,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -68,10 +71,12 @@ Future<dynamic> showApprovalDialog(
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: selectedAction == "reject" backgroundColor:
selectedAction == "reject"
? Colors.redAccent ? Colors.redAccent
: Colors.grey.shade200, : Colors.grey.shade200,
foregroundColor: selectedAction == "reject" foregroundColor:
selectedAction == "reject"
? Colors.white ? Colors.white
: Colors.black, : Colors.black,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -122,12 +127,16 @@ Future<dynamic> showApprovalDialog(
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide( borderSide: const BorderSide(
color: Colors.blueGrey, width: 0.5), color: Colors.blueGrey,
width: 0.5,
),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: borderSide: BorderSide(
BorderSide(color: Colors.blueGrey, width: 0.5), color: Colors.blueGrey,
width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@ -135,7 +144,7 @@ Future<dynamic> showApprovalDialog(
), ),
), ),
), ),
] ],
], ],
), ),
actionsAlignment: MainAxisAlignment.center, actionsAlignment: MainAxisAlignment.center,
@ -187,14 +196,12 @@ Future<dynamic> showApprovalDialog(
Future<bool?> showApproveDialog1(BuildContext context, Color layoutColor) { Future<bool?> showApproveDialog1(BuildContext context, Color layoutColor) {
return showDialog<bool>( return showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder:
(context) => AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
title: const Text( title: const Text(
"Confirm Approval", "Confirm Approval",
style: TextStyle( style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
fontSize: 18,
fontWeight: FontWeight.bold,
),
), ),
content: const Text("Are you sure you want to approve this plan?"), content: const Text("Are you sure you want to approve this plan?"),
actions: [ actions: [
@ -231,14 +238,17 @@ Future<bool?> showApproveDialog1(BuildContext context, Color layoutColor) {
/// Show confirm dialog for rejection with remarks input /// Show confirm dialog for rejection with remarks input
Future<String?> showRejectDialog1( Future<String?> showRejectDialog1(
BuildContext context, Color layoutColor) async { BuildContext context,
Color layoutColor,
) async {
String remarks = ""; String remarks = "";
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
builder: (context) { builder: (context) {
return StatefulBuilder( return StatefulBuilder(
builder: (context, setState) => AlertDialog( builder:
(context, setState) => AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
contentPadding: const EdgeInsets.all(36), contentPadding: const EdgeInsets.all(36),
// title: const Text("Confirm Rejection"), // title: const Text("Confirm Rejection"),
@ -260,13 +270,20 @@ Future<String?> showRejectDialog1(
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 10, // 👈 Set your desired font size here fontSize: 10, // 👈 Set your desired font size here
color: Colors.grey, color: Colors.grey,
fontFamily: "Inter", // optional if you want consistent font fontFamily:
"Inter", // optional if you want consistent font
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blueGrey, width: 0.5), borderSide: BorderSide(
color: Colors.blueGrey,
width: 0.5,
),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blueGrey, width: 0.5), borderSide: BorderSide(
color: Colors.blueGrey,
width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey, width: 1), borderSide: BorderSide(color: Colors.grey, width: 1),

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,8 @@ import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../../services/apiService.dart';
class LoginWidget extends StatefulWidget { class LoginWidget extends StatefulWidget {
final bool isDesktop; final bool isDesktop;
final bool isTablet; final bool isTablet;
@ -25,6 +27,9 @@ class LoginWidget extends StatefulWidget {
enum LoginStep { login, forgotEmail, otpReset } enum LoginStep { login, forgotEmail, otpReset }
class _LoginWidgetState extends State<LoginWidget> { class _LoginWidgetState extends State<LoginWidget> {
final ApiService apiService = ApiService();
bool _moved = false;
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final TextEditingController _emailController = TextEditingController(); final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController(); final TextEditingController _passwordController = TextEditingController();
@ -39,6 +44,17 @@ class _LoginWidgetState extends State<LoginWidget> {
// 🔹 Login Step Enum and State Variable // 🔹 Login Step Enum and State Variable
LoginStep _loginStep = LoginStep.login; LoginStep _loginStep = LoginStep.login;
@override
void initState() {
super.initState();
Future.delayed(Duration(milliseconds: 300), () {
setState(() {
_moved = true;
});
});
}
@override @override
void dispose() { void dispose() {
_emailController.dispose(); _emailController.dispose();
@ -54,15 +70,18 @@ class _LoginWidgetState extends State<LoginWidget> {
final parts = token.split('.'); final parts = token.split('.');
if (parts.length != 3) throw Exception('Invalid token format'); if (parts.length != 3) throw Exception('Invalid token format');
final payload = json final payload = json.decode(
.decode(utf8.decode(base64Url.decode(base64Url.normalize(parts[1])))); utf8.decode(base64Url.decode(base64Url.normalize(parts[1]))),
);
final userData = payload['data']; final userData = payload['data'];
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', token); await prefs.setString('auth_token', token);
await prefs.setString( await prefs.setString(
'user_data', jsonEncode(userData)); // Store full user data 'user_data',
jsonEncode(userData),
); // Store full user data
if (userData != null) { if (userData != null) {
final pref = await SharedPreferences.getInstance(); final pref = await SharedPreferences.getInstance();
@ -75,6 +94,8 @@ class _LoginWidgetState extends State<LoginWidget> {
print("userData11 - ${userData['role']}"); print("userData11 - ${userData['role']}");
print("userData12 - $userRole"); print("userData12 - $userRole");
} }
await apiService.getOrganizationData();
} catch (e) { } catch (e) {
print('Error decoding token: $e'); print('Error decoding token: $e');
} }
@ -89,7 +110,7 @@ class _LoginWidgetState extends State<LoginWidget> {
Uri.parse(url), Uri.parse(url),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
}, },
body: jsonEncode({ body: jsonEncode({
'email': _emailController.text.trim(), 'email': _emailController.text.trim(),
@ -135,6 +156,8 @@ class _LoginWidgetState extends State<LoginWidget> {
backgroundColor: Colors.red, backgroundColor: Colors.red,
), ),
); );
_emailController.clear();
_passwordController.clear();
// Fluttertoast.showToast( // Fluttertoast.showToast(
// msg: "Login Failed: $errorMessage", // msg: "Login Failed: $errorMessage",
// toastLength: Toast.LENGTH_LONG, // toastLength: Toast.LENGTH_LONG,
@ -145,9 +168,9 @@ class _LoginWidgetState extends State<LoginWidget> {
// ); // );
} }
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text("Error: $e")), context,
); ).showSnackBar(SnackBar(content: Text("Error: $e")));
} }
} }
} }
@ -175,19 +198,27 @@ class _LoginWidgetState extends State<LoginWidget> {
// _clearAllFields(); // _clearAllFields();
}); });
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("OTP sent to your email")), SnackBar(
content: Text("OTP sent to your email"),
backgroundColor: Colors.green,
),
); );
} else { } else {
print(response); print(response);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: content: Text(
Text("${jsonDecode(response.body)['messages']['error']}")), "${jsonDecode(response.body)['messages']['error']}",
),
backgroundColor: Colors.red,
),
); );
} }
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context).showSnackBar(
.showSnackBar(SnackBar(content: Text("Error: $e"))); SnackBar(content: Text("Error: $e"), backgroundColor: Colors.red),
);
} }
} else if (!_isForgotPassword && _showOtpResetFields) { } else if (!_isForgotPassword && _showOtpResetFields) {
print('22'); print('22');
@ -214,20 +245,32 @@ class _LoginWidgetState extends State<LoginWidget> {
_clearAllFields(); _clearAllFields();
}); });
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Password reset successfully")), SnackBar(
content: Text("Password reset successfully"),
backgroundColor: Colors.green,
),
); );
} else { } else {
print('23'); print('23');
print('otp wrong'); print('otp wrong');
final responseBody = json.decode(response.body);
final errorMessage =
responseBody['messages']?['error'] ?? 'An unknown error occurred';
print(errorMessage);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
"Reset failed: ${jsonDecode(response.body)['message']}")), "Reset failed: $errorMessage",
// "Reset failed: ${jsonDecode(response.body)['message']}",
),
backgroundColor: Colors.red,
),
); );
} }
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context).showSnackBar(
.showSnackBar(SnackBar(content: Text("Error: $e"))); SnackBar(content: Text("Error: $e"), backgroundColor: Colors.red),
);
} }
} else { } else {
// Login flow // Login flow
@ -255,15 +298,15 @@ class _LoginWidgetState extends State<LoginWidget> {
} }
@override @override
/// Layout /// Layout
Widget build(BuildContext context) { Widget build(BuildContext context) {
double formWidth = widget.isTablet ? 400 : 300; double formWidth = widget.isTablet ? 400 : 300;
return Container( return Container(
// color: Color(0xFF114D8B), // color: Color(0xFF114D8B),
color: Color(0xFFf5f5f5), color: Colors.white,
// color: Color(0xFFf5f5f5),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: Row( child: Row(
children: [ children: [
@ -272,21 +315,14 @@ class _LoginWidgetState extends State<LoginWidget> {
flex: 2, flex: 2,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
// image: DecorationImage(
// image: AssetImage(
// 'assets/images/login/login_travel.png',
// ),
// // fit: BoxFit.fill
// fit: BoxFit.contain
// // fit: BoxFit.cover, // or BoxFit.contain, BoxFit.fill, etc.
// ),
color: Color(0xFFE6F0FA), color: Color(0xFFE6F0FA),
// color: Color(0xFFF0F7FF), // color: Color(0xFFF0F7FF),
// color: Colors.white, // color: Colors.white,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topRight: Radius.circular(250), // Rounded top-left corner topRight: Radius.circular(250), // Rounded top-left corner
bottomRight: bottomRight: Radius.circular(
Radius.circular(250), // Rounded bottom-left corner 250,
), // Rounded bottom-left corner
), ),
), ),
// child: Padding( // child: Padding(
@ -305,11 +341,15 @@ class _LoginWidgetState extends State<LoginWidget> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Image.asset( // Image.asset(
'assets/images/login/logoNew.jpg', // 'assets/images/login/logoNew.jpg',
// width: 200, // Optional: control size
// height: 100,
// fit: BoxFit.contain,
// ),
Container(
width: 200, // Optional: control size width: 200, // Optional: control size
height: 100, height: 100,
fit: BoxFit.contain,
), ),
Expanded( Expanded(
child: Container( child: Container(
@ -320,9 +360,10 @@ class _LoginWidgetState extends State<LoginWidget> {
// width: 200, // Optional: control size // width: 200, // Optional: control size
// height: 100, // height: 100,
fit: BoxFit.contain, fit: BoxFit.contain,
)),
), ),
) ),
),
),
], ],
), ),
), ),
@ -332,20 +373,18 @@ class _LoginWidgetState extends State<LoginWidget> {
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
// color: Colors.white, // color: Colors.white,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(25), // Rounded top-left corner topLeft: Radius.circular(25), // Rounded top-left corner
bottomLeft: Radius.circular(25), // Rounded bottom-left corner bottomLeft: Radius.circular(25), // Rounded bottom-left corner
), ),
), ),
child: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(40), padding: const EdgeInsets.all(10),
child: _buildForm(width: formWidth), // Fixed form width child: _buildForm(width: formWidth), // Fixed form width
), ),
), ),
), ),
),
], ],
), ),
); );
@ -353,29 +392,57 @@ class _LoginWidgetState extends State<LoginWidget> {
/// **Reusable Login Form** /// **Reusable Login Form**
Widget _buildForm({required double width}) { Widget _buildForm({required double width}) {
return SizedBox( return Column(
children: [
// width: width,
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
//
// ],
// ),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: width, width: width,
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Image.asset(
'assets/images/login/logoNew.jpg',
width: 180, // Optional: control size
height: 70,
fit: BoxFit.contain,
),
const SizedBox(height: 2),
Text( Text(
"Sign In", "Sign In",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 18, fontSize: widget.isDesktop ? 20 : 18,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w700,
color: Color(0xFF212121)), color: Colors.green,
// color: Color(0xFF212121),
), ),
const SizedBox(height: 2), ),
const SizedBox(height: 5),
Text( Text(
"Welcome To TripApprovalTool", "Welcome To TripApprovalTool",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Color(0xFF212121)),
color: Color(0xFF212121),
), ),
const SizedBox(height: 18), ),
const SizedBox(height: 10),
/// **Email Field** /// **Email Field**
if (!_isForgotPassword && !_showOtpResetFields) ...[ if (!_isForgotPassword && !_showOtpResetFields) ...[
@ -383,47 +450,100 @@ class _LoginWidgetState extends State<LoginWidget> {
TextFormField( TextFormField(
controller: _emailController, controller: _emailController,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w600, fontSize: 11), fontWeight: FontWeight.w600,
decoration: fontSize: 11,
_inputDecoration("Enter your email address").copyWith( ),
decoration: _inputDecoration(
"Enter your email address",
).copyWith(
prefixIcon: Icon( prefixIcon: Icon(
Icons.email_outlined, Icons.email_outlined,
size: 16, size: 16,
), ),
), ),
validator: (value) => validator:
value == null || value.isEmpty ? 'Required Email' : null, (value) =>
value == null || value.isEmpty
? 'Required Email'
: null,
), ),
const SizedBox(height: 16), const SizedBox(height: 10),
/// **Password Field** /// **Password Field**
_buildLabel("Password"), _buildLabel("Password"),
// TextFormField(
// controller: _passwordController,
// style: GoogleFonts.poppins(
// fontWeight: FontWeight.w600,
// fontSize: 11,
// ),
// obscureText: _obscureText,
//
// decoration: _inputDecoration(
// "Enter your password",
// ).copyWith(
// prefixIcon: Icon(Icons.key, size: 16),
// suffixIcon: IconButton(
// icon: Icon(
// _obscureText
// ? Icons.visibility_off
// : Icons.visibility,
// color: Color(0xFF12B24B),
// size: 16,
// ),
//
// onPressed:
// () => setState(
// () => _obscureText = !_obscureText,
// ),
// ),
// ),
//
// validator:
// (value) =>
// value == null || value.isEmpty
// ? 'Required Password'
// : null,
// ),
TextFormField( TextFormField(
controller: _passwordController, controller: _passwordController,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w600, fontSize: 11), fontWeight: FontWeight.w600,
obscureText: _obscureText, fontSize: 11,
decoration: _inputDecoration("Enter your password").copyWith(
prefixIcon: Icon(
Icons.key,
size: 16,
), ),
obscureText: _obscureText,
textInputAction: TextInputAction.done,
decoration: _inputDecoration(
"Enter your password",
).copyWith(
prefixIcon: Icon(Icons.key, size: 16),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
_obscureText ? Icons.visibility_off : Icons.visibility, _obscureText
? Icons.visibility_off
: Icons.visibility,
color: Color(0xFF12B24B), color: Color(0xFF12B24B),
size: 16, size: 16,
), ),
onPressed: () => onPressed:
setState(() => _obscureText = !_obscureText), () => setState(
() => _obscureText = !_obscureText,
), ),
), ),
validator: (value) =>
value == null || value.isEmpty ? 'Required Password' : null,
), ),
onFieldSubmitted: (_) {
const SizedBox(height: 20), if (_formKey.currentState!.validate()) {
_login(context);
}
},
validator:
(value) =>
value == null || value.isEmpty
? 'Required Password'
: null,
),
const SizedBox(height: 10),
/// **Login Button** /// **Login Button**
Row( Row(
@ -432,31 +552,39 @@ class _LoginWidgetState extends State<LoginWidget> {
child: ElevatedButton( child: ElevatedButton(
onPressed: () => _login(context), onPressed: () => _login(context),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF12B24B), // Button color backgroundColor: Color(
foregroundColor: Colors.white, // Text color 0xFF12B24B,
), // Button color
foregroundColor:
Colors.white, // Text color
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12), horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18)), borderRadius: BorderRadius.circular(18),
),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 10), horizontal: 24,
vertical: 5,
),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
"Sign In", "Sign In",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w800, fontSize: 15), fontWeight: FontWeight.w800,
fontSize: 13.5,
), ),
SizedBox(
width: 3,
), ),
SizedBox(width: 3),
Icon( Icon(
Icons.arrow_forward_sharp, Icons.arrow_forward_sharp,
color: Colors.white, color: Colors.white,
) ),
], ],
), ),
), ),
@ -464,54 +592,69 @@ class _LoginWidgetState extends State<LoginWidget> {
), ),
], ],
), ),
] else if (_isForgotPassword && !_showOtpResetFields) ...[ ] else if (_isForgotPassword &&
!_showOtpResetFields) ...[
_buildLabel("Email Address"), _buildLabel("Email Address"),
TextFormField( TextFormField(
controller: _emailController, controller: _emailController,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w600, fontSize: 11), fontWeight: FontWeight.w600,
decoration: fontSize: 11,
_inputDecoration("Enter your email address").copyWith( ),
decoration: _inputDecoration(
"Enter your email address",
).copyWith(
prefixIcon: Icon( prefixIcon: Icon(
Icons.email_outlined, Icons.email_outlined,
size: 16, size: 16,
), ),
), ),
validator: (value) => validator:
value == null || value.isEmpty ? 'Required Email' : null, (value) =>
value == null || value.isEmpty
? 'Required Email'
: null,
), ),
const SizedBox(height: 20), const SizedBox(height: 10),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
onPressed: () => _onSubmit(context), onPressed: () => _onSubmit(context),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF12B24B), // Button color backgroundColor: Color(
foregroundColor: Colors.white, // Text color 0xFF12B24B,
), // Button color
foregroundColor:
Colors.white, // Text color
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12), horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18)), borderRadius: BorderRadius.circular(18),
),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 10), horizontal: 24,
vertical: 5,
),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
"Submit", "Submit",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w800, fontSize: 15), fontWeight: FontWeight.w800,
fontSize: 13.5,
), ),
SizedBox(
width: 3,
), ),
SizedBox(width: 3),
Icon( Icon(
Icons.arrow_forward_sharp, Icons.arrow_forward_sharp,
color: Colors.white, color: Colors.white,
) ),
], ],
), ),
), ),
@ -525,80 +668,103 @@ class _LoginWidgetState extends State<LoginWidget> {
controller: _emailController, controller: _emailController,
readOnly: true, readOnly: true,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w600, fontSize: 11), fontWeight: FontWeight.w600,
decoration: fontSize: 11,
_inputDecoration("Enter your email address").copyWith( ),
decoration: _inputDecoration(
"Enter your email address",
).copyWith(
prefixIcon: Icon( prefixIcon: Icon(
Icons.email_outlined, Icons.email_outlined,
size: 16, size: 16,
), ),
), ),
validator: (value) => validator:
value == null || value.isEmpty ? 'Required Email' : null, (value) =>
value == null || value.isEmpty
? 'Required Email'
: null,
), ),
const SizedBox(height: 16), const SizedBox(height: 10),
_buildLabel("OTP"), _buildLabel("OTP"),
TextFormField( TextFormField(
controller: _otpController, controller: _otpController,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w600, fontSize: 11), fontWeight: FontWeight.w600,
decoration: _inputDecoration("Enter your OTP").copyWith( fontSize: 11,
),
decoration: _inputDecoration(
"Enter your OTP",
).copyWith(
prefixIcon: Icon( prefixIcon: Icon(
Icons.email_outlined, Icons.email_outlined,
size: 16, size: 16,
), ),
), ),
validator: (value) => validator:
value == null || value.isEmpty ? 'Required OTP' : null, (value) =>
value == null || value.isEmpty
? 'Required OTP'
: null,
), ),
const SizedBox(height: 16), const SizedBox(height: 10),
_buildLabel("New Password"), _buildLabel("New Password"),
TextFormField( TextFormField(
controller: _newPasswordController, controller: _newPasswordController,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w600, fontSize: 11), fontWeight: FontWeight.w600,
obscureText: _obscureText, fontSize: 11,
decoration:
_inputDecoration("Enter your new password").copyWith(
prefixIcon: Icon(
Icons.key,
size: 16,
), ),
obscureText: _obscureText,
decoration: _inputDecoration(
"Enter your new password",
).copyWith(
prefixIcon: Icon(Icons.key, size: 16),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
_obscureText ? Icons.visibility_off : Icons.visibility, _obscureText
? Icons.visibility_off
: Icons.visibility,
color: Color(0xFF12B24B), color: Color(0xFF12B24B),
size: 16, size: 16,
), ),
onPressed: () => onPressed:
setState(() => _obscureText = !_obscureText), () => setState(
() => _obscureText = !_obscureText,
), ),
), ),
validator: (value) => value == null || value.isEmpty ),
validator:
(value) =>
value == null || value.isEmpty
? 'Required New Password' ? 'Required New Password'
: null, : null,
), ),
const SizedBox(height: 16), const SizedBox(height: 10),
_buildLabel("Confirm Password"), _buildLabel("Confirm Password"),
TextFormField( TextFormField(
controller: _confirmPasswordController, controller: _confirmPasswordController,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w600, fontSize: 11), fontWeight: FontWeight.w600,
obscureText: _obscureText, fontSize: 11,
decoration:
_inputDecoration("Enter your confirm password").copyWith(
prefixIcon: Icon(
Icons.key,
size: 16,
), ),
obscureText: _obscureText,
decoration: _inputDecoration(
"Enter your confirm password",
).copyWith(
prefixIcon: Icon(Icons.key, size: 16),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
_obscureText ? Icons.visibility_off : Icons.visibility, _obscureText
? Icons.visibility_off
: Icons.visibility,
color: Color(0xFF12B24B), color: Color(0xFF12B24B),
size: 16, size: 16,
), ),
onPressed: () => onPressed:
setState(() => _obscureText = !_obscureText), () => setState(
() => _obscureText = !_obscureText,
),
), ),
), ),
validator: (value) { validator: (value) {
@ -611,13 +777,14 @@ class _LoginWidgetState extends State<LoginWidget> {
return null; return null;
}, },
), ),
const SizedBox(height: 20), const SizedBox(height: 10),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
if (!_formKey.currentState!.validate()) return; if (!_formKey.currentState!.validate())
return;
setState(() { setState(() {
_isForgotPassword = false; _isForgotPassword = false;
_showOtpResetFields = true; _showOtpResetFields = true;
@ -626,31 +793,39 @@ class _LoginWidgetState extends State<LoginWidget> {
_onSubmit(context); _onSubmit(context);
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF12B24B), // Button color backgroundColor: Color(
foregroundColor: Colors.white, // Text color 0xFF12B24B,
), // Button color
foregroundColor:
Colors.white, // Text color
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12), horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18)), borderRadius: BorderRadius.circular(18),
),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 10), horizontal: 24,
vertical: 10,
),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
"Submit", "Submit",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w800, fontSize: 15), fontWeight: FontWeight.w800,
fontSize: 13.5,
), ),
SizedBox(
width: 3,
), ),
SizedBox(width: 3),
Icon( Icon(
Icons.arrow_forward_sharp, Icons.arrow_forward_sharp,
color: Colors.white, color: Colors.white,
) ),
], ],
), ),
), ),
@ -659,7 +834,7 @@ class _LoginWidgetState extends State<LoginWidget> {
], ],
), ),
], ],
const SizedBox(height: 20), const SizedBox(height: 10),
if (!_isForgotPassword && !_showOtpResetFields) if (!_isForgotPassword && !_showOtpResetFields)
Center( Center(
child: TextButton( child: TextButton(
@ -678,7 +853,8 @@ class _LoginWidgetState extends State<LoginWidget> {
color: Color(0xFF212121), // Text color color: Color(0xFF212121), // Text color
decoration: decoration:
TextDecoration.underline, // Underline the text TextDecoration
.underline, // Underline the text
), ),
), ),
), ),
@ -701,13 +877,14 @@ class _LoginWidgetState extends State<LoginWidget> {
color: Color(0xFF212121), // Text color color: Color(0xFF212121), // Text color
decoration: decoration:
TextDecoration.underline, // Underline the text TextDecoration
.underline, // Underline the text
), ),
), ),
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 10),
if (!_isForgotPassword && !_showOtpResetFields) if (!_isForgotPassword && !_showOtpResetFields)
Row( Row(
children: [ children: [
@ -717,10 +894,14 @@ class _LoginWidgetState extends State<LoginWidget> {
handleMS(); handleMS();
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.white, // Button color backgroundColor:
foregroundColor: Colors.black, // Text color Colors.white, // Button color
foregroundColor:
Colors.black, // Text color
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 5), horizontal: 24,
vertical: 5,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18), borderRadius: BorderRadius.circular(18),
side: BorderSide( side: BorderSide(
@ -731,18 +912,20 @@ class _LoginWidgetState extends State<LoginWidget> {
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 5), horizontal: 24,
vertical: 3,
),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
"Sign In With Microsoft", "Sign In With Microsoft",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontWeight: FontWeight.w500, fontSize: 14), fontWeight: FontWeight.w500,
fontSize: 13,
), ),
SizedBox(
width: 3,
), ),
SizedBox(width: 3),
Image.asset( Image.asset(
'assets/images/login/microsoft.png', 'assets/images/login/microsoft.png',
width: 30, // Optional: control size width: 30, // Optional: control size
@ -783,6 +966,13 @@ class _LoginWidgetState extends State<LoginWidget> {
], ],
), ),
), ),
),
],
),
],
),
),
],
); );
} }
@ -797,7 +987,8 @@ class _LoginWidgetState extends State<LoginWidget> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121)), color: Color(0xFF212121),
),
), ),
), ),
); );
@ -826,8 +1017,10 @@ class _LoginWidgetState extends State<LoginWidget> {
final url = '$apiUrl/auth/mslogin'; final url = '$apiUrl/auth/mslogin';
print(url); print(url);
try { try {
final response = await http final response = await http.get(
.get(Uri.parse(url), headers: {'Content-Type': 'application/json'}); Uri.parse(url),
headers: {'Content-Type': 'application/json'},
);
print("inside try method"); print("inside try method");
if (response.statusCode == 200) { if (response.statusCode == 200) {
final authUrl = json.decode(response.body)['auth_url']; final authUrl = json.decode(response.body)['auth_url'];

View File

@ -19,13 +19,14 @@ class CostCenterData extends StatefulWidget {
final int? costcenterId; // <-- Add this final int? costcenterId; // <-- Add this
final Map<String, dynamic>? costcenterData; final Map<String, dynamic>? costcenterData;
const CostCenterData( const CostCenterData({
{super.key, super.key,
required this.isDesktop, required this.isDesktop,
this.layoutColor, this.layoutColor,
required this.fetchGetCostCenter, required this.fetchGetCostCenter,
this.costcenterId, this.costcenterId,
this.costcenterData}); this.costcenterData,
});
@override @override
CostCenterDataState createState() => CostCenterDataState(); CostCenterDataState createState() => CostCenterDataState();
@ -49,10 +50,7 @@ class CostCenterDataState extends State<CostCenterData> {
int? costcenterDataId; int? costcenterDataId;
late String isActive = "1"; late String isActive = "1";
List<String> dataHeader = [ List<String> dataHeader = ["name", "description"];
"name",
"description",
];
Map<String, dynamic> costcenterDetails() { Map<String, dynamic> costcenterDetails() {
final data = { final data = {
@ -69,7 +67,6 @@ class CostCenterDataState extends State<CostCenterData> {
void initState() { void initState() {
super.initState(); super.initState();
apiData = null; apiData = null;
for (var field in dataHeader) { for (var field in dataHeader) {
controllers[field] = TextEditingController(); controllers[field] = TextEditingController();
@ -110,7 +107,6 @@ class CostCenterDataState extends State<CostCenterData> {
}); });
} }
void toggleStatus() { void toggleStatus() {
setState(() { setState(() {
isActive = isActive == "1" ? "0" : "1"; isActive = isActive == "1" ? "0" : "1";
@ -171,7 +167,9 @@ class CostCenterDataState extends State<CostCenterData> {
apiUrldata = '$apiUrl/api/updateCostCenter/$costcenterDataId'; apiUrldata = '$apiUrl/api/updateCostCenter/$costcenterDataId';
costcenterData["cost_center_id"] = costcenterDataId.toString(); costcenterData["cost_center_id"] = costcenterDataId.toString();
costcenterData["updated_by"] = userId; costcenterData["updated_by"] = userId;
(costcenterData.containsKey("created_by")) ? costcenterData.remove("created_by") : '' ; (costcenterData.containsKey("created_by"))
? costcenterData.remove("created_by")
: '';
} else { } else {
print("for add CostCenter id - null"); print("for add CostCenter id - null");
apiUrldata = '$apiUrl/api/createCostCenter'; apiUrldata = '$apiUrl/api/createCostCenter';
@ -193,11 +191,11 @@ class CostCenterDataState extends State<CostCenterData> {
}; };
final body = jsonEncode(costcenterData); final body = jsonEncode(costcenterData);
final response = costcenterDataId != null final response =
costcenterDataId != null
? await http.put(uri, headers: headers, body: body) ? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body); : await http.post(uri, headers: headers, body: body);
switch (response.statusCode) { switch (response.statusCode) {
case 200: case 200:
print("Update - Response: ${response.body}"); print("Update - Response: ${response.body}");
@ -217,7 +215,6 @@ class CostCenterDataState extends State<CostCenterData> {
print("Failed to submit costcenter. Status: ${response.statusCode}"); print("Failed to submit costcenter. Status: ${response.statusCode}");
print("Error: ${response.body}"); print("Error: ${response.body}");
} }
} catch (e) { } catch (e) {
print(" Error submitting plan: $e"); print(" Error submitting plan: $e");
} }
@ -225,7 +222,6 @@ class CostCenterDataState extends State<CostCenterData> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
@ -238,27 +234,27 @@ class CostCenterDataState extends State<CostCenterData> {
Row( Row(
children: [ children: [
Text( Text(
(costcenterDataId != null) ? 'Edit CostCenter' : 'Create CostCenter', (costcenterDataId != null)
? 'Edit CostCenter'
: 'Create CostCenter',
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
), ),
const Spacer(), const Spacer(),
], ],
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Divider( Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
const SizedBox(height: 5), const SizedBox(height: 5),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Name", "Name *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -278,7 +274,8 @@ class CostCenterDataState extends State<CostCenterData> {
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), ),
),
), ),
if (errorMessages["name"] != null) ...[ if (errorMessages["name"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -289,18 +286,17 @@ class CostCenterDataState extends State<CostCenterData> {
], ],
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Description", "Description *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -334,9 +330,7 @@ class CostCenterDataState extends State<CostCenterData> {
], ],
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
if (costcenterDataId != null) if (costcenterDataId != null)
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -346,7 +340,8 @@ class CostCenterDataState extends State<CostCenterData> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
Tooltip( Tooltip(
message: message:
@ -358,17 +353,14 @@ class CostCenterDataState extends State<CostCenterData> {
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
color: isActive == "1" ? Colors.green : Colors.red, color: isActive == "1" ? Colors.green : Colors.grey,
),
), ),
), ),
), ),
)
], ],
), ),
if (costcenterDataId != null) if (costcenterDataId != null) SizedBox(height: 15),
SizedBox(
height: 15,
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
@ -405,13 +397,17 @@ class CostCenterDataState extends State<CostCenterData> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
), ),
child: Text('Save', child: Text(
'Save',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)), fontSize: 11,
color: Colors.white,
),
),
), ),
), ),
], ],
) ),
// : SizedBox.shrink(), // : SizedBox.shrink(),
], ],
), ),

View File

@ -67,11 +67,13 @@ class CostCenterListState extends State<CostCenterList> {
String? bodyStringColor = await getBodyColor(); String? bodyStringColor = await getBodyColor();
setState(() { setState(() {
layoutColor = layoutString != null layoutColor =
layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyStringColor != null bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
@ -97,8 +99,7 @@ class CostCenterListState extends State<CostCenterList> {
} }
Future<List<dynamic>> fetchGetCostCenter() async { Future<List<dynamic>> fetchGetCostCenter() async {
final String apiUrlData = '$apiUrl/api/getCostCenterMaster?for=table_view';
final String apiUrlData = '$apiUrl/api/getCostCenterMaster';
final String? token = await getToken(); final String? token = await getToken();
@ -143,25 +144,30 @@ class CostCenterListState extends State<CostCenterList> {
print("all before filtering: $query"); print("all before filtering: $query");
final lowerQuery = query.toLowerCase(); final lowerQuery = query.toLowerCase();
setState(() { setState(() {
filteredCostCenter = allCostCenter.where((object) { filteredCostCenter =
allCostCenter.where((object) {
final isActiveStatus = final isActiveStatus =
object['is_active'] == "1" ? "active" : "inactive"; object['is_active'] == "1" ? "active" : "inactive";
return (object['cost_center_id']?.toLowerCase().contains(lowerQuery) ?? return (object['cost_center_id']?.toLowerCase().contains(
lowerQuery,
) ??
false) || false) ||
(object['name']?.toLowerCase().contains(lowerQuery) ?? (object['name']?.toLowerCase().contains(lowerQuery) ?? false) ||
(object['description']?.toLowerCase().contains(lowerQuery) ??
false) || false) ||
(object['description']?.toLowerCase().contains(lowerQuery) ?? false) ||
(isActiveStatus.contains(lowerQuery)); (isActiveStatus.contains(lowerQuery));
}).toList(); }).toList();
currentPage = 0;
}); });
print("filteredCostCenter: $filteredCostCenter"); print("filteredCostCenter: $filteredCostCenter");
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFf5f5f5), backgroundColor: Color(0xFFf5f5f5),
@ -170,11 +176,14 @@ class CostCenterListState extends State<CostCenterList> {
appBar: CustomAppBar(isDesktop: isDesktop), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding:
isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height * vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
@ -187,7 +196,8 @@ class CostCenterListState extends State<CostCenterList> {
), ),
), ),
); );
}); },
);
} }
Widget buildGroupList(bool isDesktop) { Widget buildGroupList(bool isDesktop) {
@ -215,7 +225,8 @@ class CostCenterListState extends State<CostCenterList> {
// ? EdgeInsets.all(10.0) // ? EdgeInsets.all(10.0)
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10), // padding: const EdgeInsets.all(10),
height: isDesktop height:
isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
@ -237,7 +248,7 @@ class CostCenterListState extends State<CostCenterList> {
Row( Row(
children: [ children: [
Text( Text(
'CostCenter Details', 'Cost Center Details',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14, fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -247,9 +258,7 @@ class CostCenterListState extends State<CostCenterList> {
], ],
), ),
if (isDesktop) if (isDesktop)
SizedBox( SizedBox(width: MediaQuery.of(context).size.width * 0.16),
width: MediaQuery.of(context).size.width * 0.16,
),
if (isDesktop) if (isDesktop)
Container( Container(
@ -261,7 +270,9 @@ class CostCenterListState extends State<CostCenterList> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -273,17 +284,19 @@ class CostCenterListState extends State<CostCenterList> {
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, width: 0.5), color: Colors.grey.shade200,
width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
// SizedBox(width: 16), // SizedBox(width: 16),
@ -297,16 +310,18 @@ class CostCenterListState extends State<CostCenterList> {
disabledForegroundColor: Colors.white, disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
side: side: BorderSide(color: Color(0xFF114D8B), width: 2),
BorderSide(color: Color(0xFF114D8B), width: 2),
), ),
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 20, vertical: 12), horizontal: 20,
vertical: 12,
),
), ),
onPressed: () async { onPressed: () async {
showDialog( showDialog(
context: context, context: context,
builder: (context) => CostCenterData( builder:
(context) => CostCenterData(
isDesktop: isDesktop, isDesktop: isDesktop,
layoutColor: layoutColor!, layoutColor: layoutColor!,
fetchGetCostCenter: refreshData, fetchGetCostCenter: refreshData,
@ -338,10 +353,7 @@ class CostCenterListState extends State<CostCenterList> {
], ],
), ),
if (!isDesktop) if (!isDesktop) SizedBox(height: 5),
SizedBox(
height: 5,
),
isDesktop isDesktop
? SizedBox.shrink() ? SizedBox.shrink()
: Row( : Row(
@ -356,7 +368,9 @@ class CostCenterListState extends State<CostCenterList> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -369,17 +383,18 @@ class CostCenterListState extends State<CostCenterList> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, color: Colors.grey.shade200,
width: 0.5), width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
// SizedBox(width: 16), // SizedBox(width: 16),
@ -417,14 +432,17 @@ class CostCenterListState extends State<CostCenterList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.grey), color: Colors.grey,
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
"Please Create CostCenter Details", "Please Create CostCenter Details",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 16, color: Colors.grey), fontSize: 16,
color: Colors.grey,
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
], ],
@ -434,19 +452,21 @@ class CostCenterListState extends State<CostCenterList> {
} }
/* Here collect the list to displayed the data in table or card Used */ /* Here collect the list to displayed the data in table or card Used */
List<dynamic> object = List<dynamic> object =
filteredCostCenter.isNotEmpty ? filteredCostCenter : allCostCenter; filteredCostCenter.isNotEmpty
? filteredCostCenter
: allCostCenter;
/* List is Sorting here */ /* List is Sorting here */
object.sort((a, b) { object.sort((a, b) {
DateTime dateA = DateTime.parse(a['created_on']); DateTime dateA = DateTime.parse(a['created_on']);
DateTime dateB = DateTime.parse(b['created_on']); DateTime dateB = DateTime.parse(b['created_on']);
return dateB return dateB.compareTo(dateA); // Descending: newest first
.compareTo(dateA); // Descending: newest first
}); });
/* For pagination for list ... */ /* For pagination for list ... */
List paginatedCostCenter = object List paginatedCostCenter =
object
.skip(currentPage * itemsPerPage) .skip(currentPage * itemsPerPage)
.take(itemsPerPage) .take(itemsPerPage)
.toList(); .toList();
@ -454,8 +474,7 @@ class CostCenterListState extends State<CostCenterList> {
/* Table ... */ /* Table ... */
Widget table = LayoutBuilder( Widget table = LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
double minWidth = double minWidth = isDesktop ? constraints.maxWidth : 1300;
isDesktop ? constraints.maxWidth : 1300;
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints(minWidth: minWidth), constraints: BoxConstraints(minWidth: minWidth),
@ -464,7 +483,9 @@ class CostCenterListState extends State<CostCenterList> {
columnSpacing: isDesktop ? 24.0 : 16.0, columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder( border: TableBorder(
horizontalInside: BorderSide( horizontalInside: BorderSide(
width: 0.5, color: Colors.grey.shade200), width: 0.5,
color: Colors.grey.shade200,
),
), ),
columns: [ columns: [
DataColumn( DataColumn(
@ -472,48 +493,68 @@ class CostCenterListState extends State<CostCenterList> {
'Name', 'Name',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Description', 'Description',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Status', 'Status',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Actions', 'Actions',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
], ],
rows: paginatedCostCenter.map((tableObject) { rows:
String costcenterId = tableObject['cost_center_id'] paginatedCostCenter.map((tableObject) {
String costcenterId =
tableObject['cost_center_id']
.toString(); // Get user ID .toString(); // Get user ID
bool isSelected = selectedCostCenterId == costcenterId; bool isSelected =
selectedCostCenterId == costcenterId;
return DataRow(cells: [ return DataRow(
DataCell(Text(tableObject['name'] ?? '', cells: [
DataCell(
Text(
tableObject['name'] ?? '',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text(tableObject['description'] ?? 'N/A', ),
),
DataCell(
Text(
tableObject['description'] ?? 'N/A',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis,
),
),
DataCell( DataCell(
Text( Text(
tableObject['is_active'] == "1" tableObject['is_active'] == "1"
@ -522,7 +563,10 @@ class CostCenterListState extends State<CostCenterList> {
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
color: tableObject['is_active'] == "1" ? Colors.green : Colors.red, color:
tableObject['is_active'] == "1"
? Colors.green
: Colors.grey,
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -535,32 +579,45 @@ class CostCenterListState extends State<CostCenterList> {
// apiService.getSingleUser(id), // apiService.getSingleUser(id),
// ), // ),
GestureDetector( GestureDetector(
child: Tooltip(
message: 'Edit CostCenter Details',
child: Image.asset( child: Image.asset(
'assets/images/IconsImg/edit.png', 'assets/images/IconsImg/edit.png',
width: 20, width: 20,
height: 15), height: 15,
),
),
onTap: () async { onTap: () async {
// final userId = getUserId(user['user_id']); // final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId); // final usersData = await getUserDetails(userId);
// //
final costcenterId = int.tryParse( final costcenterId = int.tryParse(
tableObject['cost_center_id'] tableObject['cost_center_id']
.toString()); .toString(),
);
if (costcenterId != null) { if (costcenterId != null) {
print("Table cell - costcenter Id -- $costcenterId"); print(
final data = await apiService.getCostCenterDetailsFind(costcenterId); "Table cell - costcenter Id -- $costcenterId",
);
final data = await apiService
.getCostCenterDetailsFind(
costcenterId,
);
print("CostCenterId -- $data"); print("CostCenterId -- $data");
showDialog( showDialog(
context: context, context: context,
builder: (context) => CostCenterData( builder:
(context) => CostCenterData(
isDesktop: isDesktop, isDesktop: isDesktop,
costcenterId: costcenterId, // Pass the ID costcenterId:
costcenterId, // Pass the ID
costcenterData: data, costcenterData: data,
layoutColor: layoutColor!, layoutColor: layoutColor!,
// fetchGetForex: fetchGetForex, // fetchGetForex: fetchGetForex,
fetchGetCostCenter: refreshData, fetchGetCostCenter:
refreshData,
// role: // role:
// "Travel Agent" // "Travel Agent"
), ),
@ -571,7 +628,8 @@ class CostCenterListState extends State<CostCenterList> {
}, },
), ),
), ),
]); ],
);
}).toList(), }).toList(),
), ),
); );
@ -587,7 +645,9 @@ class CostCenterListState extends State<CostCenterList> {
return Card( return Card(
color: Colors.white, color: Colors.white,
margin: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(
horizontal: 12, vertical: 6), horizontal: 12,
vertical: 6,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@ -607,37 +667,50 @@ class CostCenterListState extends State<CostCenterList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87, color: Colors.black87,
fontWeight: FontWeight.w700), fontWeight: FontWeight.w700,
),
), ),
GestureDetector( GestureDetector(
child: Tooltip(
message: 'Edit CostCenter Details',
child: Image.asset( child: Image.asset(
'assets/images/IconsImg/edit.png', 'assets/images/IconsImg/edit.png',
width: 20, width: 20,
height: 15), height: 15,
),
),
onTap: () async { onTap: () async {
// final userId = getUserId(user['user_id']); // final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId); // final usersData = await getUserDetails(userId);
// //
final costcenterId = int.tryParse( final costcenterId = int.tryParse(
cardObject['cost_center_id'] cardObject['cost_center_id']
.toString()); .toString(),
);
if (costcenterId != null) { if (costcenterId != null) {
print("costcenterId -- $costcenterId"); print(
"costcenterId -- $costcenterId",
);
final data = await apiService final data = await apiService
.getCostCenterDetailsFind(costcenterId); .getCostCenterDetailsFind(
costcenterId,
);
print("CostCenterId -- $data"); print("CostCenterId -- $data");
showDialog( showDialog(
context: context, context: context,
builder: (context) => CostCenterData( builder:
(context) => CostCenterData(
isDesktop: isDesktop, isDesktop: isDesktop,
costcenterId:costcenterId, // Pass the ID costcenterId:
costcenterData:data, costcenterId, // Pass the ID
layoutColor:layoutColor!, costcenterData: data,
layoutColor: layoutColor!,
// fetchGetCostCenter: fetchGetCostCenter, // fetchGetCostCenter: fetchGetCostCenter,
fetchGetCostCenter: refreshData, fetchGetCostCenter:
refreshData,
// role: // role:
// "Travel Agent" // "Travel Agent"
), ),
@ -736,13 +809,12 @@ class CostCenterListState extends State<CostCenterList> {
cardObject['description'] ?? '', cardObject['description'] ?? '',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
SizedBox( SizedBox(width: 10),
width: 10,
),
Column( Column(
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
@ -751,7 +823,8 @@ class CostCenterListState extends State<CostCenterList> {
cardObject['description'] ?? '', cardObject['description'] ?? '',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
@ -767,13 +840,13 @@ class CostCenterListState extends State<CostCenterList> {
); );
} }
return Expanded( return Expanded(
child: Column( child: Column(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: isDesktop child:
isDesktop
? (searchController.text.isNotEmpty && ? (searchController.text.isNotEmpty &&
filteredCostCenter.isEmpty filteredCostCenter.isEmpty
? Center( ? Center(
@ -781,7 +854,8 @@ class CostCenterListState extends State<CostCenterList> {
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey), color: Colors.grey,
),
), ),
) )
: SingleChildScrollView( : SingleChildScrollView(
@ -795,10 +869,13 @@ class CostCenterListState extends State<CostCenterList> {
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey), color: Colors.grey,
),
), ),
) )
: buildMobileCardView(paginatedCostCenter)), : buildMobileCardView(
paginatedCostCenter,
)),
), ),
// Expanded( // Expanded(
// child: isDesktop // child: isDesktop
@ -829,9 +906,11 @@ class CostCenterListState extends State<CostCenterList> {
), ),
); );
}, },
) ),
]), ],
)), ),
),
),
); );
} }
} }

View File

@ -197,18 +197,23 @@ class StatusDashboardState extends State<StatusDashboard> {
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal: MediaQuery.of(context).size.width * 0.1, // 30% of screen width as horizontal padding
0.1, // 30% of screen width as horizontal padding
vertical: 10, // 5% of screen height as vertical padding vertical: 10, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
child:Container( child : LayoutBuilder(
// padding: const EdgeInsets.all(10.0), builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight( // Only needed if child layout depends on height
child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDesktop ? Colors.white : const Color(0xFFFCFCFC), color: isDesktop ? Colors.white : const Color(0xFFFCFCFC),
borderRadius: BorderRadius.circular(12), // 👈 Set your desired radius borderRadius: BorderRadius.circular(12),
), ),
// color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@ -253,7 +258,15 @@ class StatusDashboardState extends State<StatusDashboard> {
), ),
], ],
), ),
),
),
),
);
},
) )
), ),
); );
}); });

View File

@ -19,13 +19,14 @@ class DepartmentData extends StatefulWidget {
final int? departmentId; // <-- Add this final int? departmentId; // <-- Add this
final Map<String, dynamic>? departmentData; final Map<String, dynamic>? departmentData;
const DepartmentData( const DepartmentData({
{super.key, super.key,
required this.isDesktop, required this.isDesktop,
this.layoutColor, this.layoutColor,
required this.fetchGetDepartment, required this.fetchGetDepartment,
this.departmentId, this.departmentId,
this.departmentData}); this.departmentData,
});
@override @override
DepartmentDataState createState() => DepartmentDataState(); DepartmentDataState createState() => DepartmentDataState();
@ -49,10 +50,7 @@ class DepartmentDataState extends State<DepartmentData> {
int? departmentDataId; int? departmentDataId;
late String isActive = "1"; late String isActive = "1";
List<String> dataHeader = [ List<String> dataHeader = ["name", "description"];
"name",
"description",
];
Map<String, dynamic> departmentDetails() { Map<String, dynamic> departmentDetails() {
final data = { final data = {
@ -69,7 +67,6 @@ class DepartmentDataState extends State<DepartmentData> {
void initState() { void initState() {
super.initState(); super.initState();
apiData = null; apiData = null;
for (var field in dataHeader) { for (var field in dataHeader) {
controllers[field] = TextEditingController(); controllers[field] = TextEditingController();
@ -110,7 +107,6 @@ class DepartmentDataState extends State<DepartmentData> {
}); });
} }
void toggleStatus() { void toggleStatus() {
setState(() { setState(() {
isActive = isActive == "1" ? "0" : "1"; isActive = isActive == "1" ? "0" : "1";
@ -171,7 +167,9 @@ class DepartmentDataState extends State<DepartmentData> {
apiUrldata = '$apiUrl/api/updateDepartment/$departmentDataId'; apiUrldata = '$apiUrl/api/updateDepartment/$departmentDataId';
departmentData["department_id"] = departmentDataId.toString(); departmentData["department_id"] = departmentDataId.toString();
departmentData["updated_by"] = userId; departmentData["updated_by"] = userId;
(departmentData.containsKey("created_by")) ? departmentData.remove("created_by") : '' ; (departmentData.containsKey("created_by"))
? departmentData.remove("created_by")
: '';
} else { } else {
print("for add Department id - null"); print("for add Department id - null");
apiUrldata = '$apiUrl/api/createDepartment'; apiUrldata = '$apiUrl/api/createDepartment';
@ -193,11 +191,11 @@ class DepartmentDataState extends State<DepartmentData> {
}; };
final body = jsonEncode(departmentData); final body = jsonEncode(departmentData);
final response = departmentDataId != null final response =
departmentDataId != null
? await http.put(uri, headers: headers, body: body) ? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body); : await http.post(uri, headers: headers, body: body);
switch (response.statusCode) { switch (response.statusCode) {
case 200: case 200:
print("Update - Response: ${response.body}"); print("Update - Response: ${response.body}");
@ -217,7 +215,6 @@ class DepartmentDataState extends State<DepartmentData> {
print("Failed to submit department. Status: ${response.statusCode}"); print("Failed to submit department. Status: ${response.statusCode}");
print("Error: ${response.body}"); print("Error: ${response.body}");
} }
} catch (e) { } catch (e) {
print(" Error submitting plan: $e"); print(" Error submitting plan: $e");
} }
@ -225,7 +222,6 @@ class DepartmentDataState extends State<DepartmentData> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
@ -238,27 +234,27 @@ class DepartmentDataState extends State<DepartmentData> {
Row( Row(
children: [ children: [
Text( Text(
(departmentDataId != null) ? 'Edit Department' : 'Create Department', (departmentDataId != null)
? 'Edit Department'
: 'Create Department',
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
), ),
const Spacer(), const Spacer(),
], ],
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Divider( Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
const SizedBox(height: 5), const SizedBox(height: 5),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Name", "Name *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -278,7 +274,8 @@ class DepartmentDataState extends State<DepartmentData> {
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), ),
),
), ),
if (errorMessages["name"] != null) ...[ if (errorMessages["name"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -289,18 +286,17 @@ class DepartmentDataState extends State<DepartmentData> {
], ],
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Description", "Description *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -334,9 +330,7 @@ class DepartmentDataState extends State<DepartmentData> {
], ],
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
if (departmentDataId != null) if (departmentDataId != null)
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -346,7 +340,8 @@ class DepartmentDataState extends State<DepartmentData> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
Tooltip( Tooltip(
message: message:
@ -362,13 +357,10 @@ class DepartmentDataState extends State<DepartmentData> {
), ),
), ),
), ),
) ),
], ],
), ),
if (departmentDataId != null) if (departmentDataId != null) SizedBox(height: 15),
SizedBox(
height: 15,
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
@ -405,13 +397,17 @@ class DepartmentDataState extends State<DepartmentData> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
), ),
child: Text('Save', child: Text(
'Save',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)), fontSize: 11,
color: Colors.white,
),
),
), ),
), ),
], ],
) ),
// : SizedBox.shrink(), // : SizedBox.shrink(),
], ],
), ),

View File

@ -67,11 +67,13 @@ class DepartmentListState extends State<DepartmentList> {
String? bodyStringColor = await getBodyColor(); String? bodyStringColor = await getBodyColor();
setState(() { setState(() {
layoutColor = layoutString != null layoutColor =
layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyStringColor != null bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
@ -97,7 +99,7 @@ class DepartmentListState extends State<DepartmentList> {
} }
Future<List<dynamic>> fetchGetDepartment() async { Future<List<dynamic>> fetchGetDepartment() async {
final String apiUrlData = '$apiUrl/api/getDepartmentList'; final String apiUrlData = '$apiUrl/api/getDepartmentList?for=table_view';
final String? token = await getToken(); final String? token = await getToken();
@ -141,24 +143,30 @@ class DepartmentListState extends State<DepartmentList> {
print("all before filtering: $query"); print("all before filtering: $query");
final lowerQuery = query.toLowerCase(); final lowerQuery = query.toLowerCase();
setState(() { setState(() {
filteredDepartment = allDepartment.where((object) { filteredDepartment =
allDepartment.where((object) {
final isActiveStatus = final isActiveStatus =
object['is_active'] == "1" ? "active" : "inactive"; object['is_active'] == "1" ? "active" : "inactive";
return (object['department_id']?.toLowerCase().contains(lowerQuery) ?? return (object['department_id']?.toLowerCase().contains(
lowerQuery,
) ??
false) || false) ||
(object['name']?.toLowerCase().contains(lowerQuery) ?? false) || (object['name']?.toLowerCase().contains(lowerQuery) ?? false) ||
(object['description']?.toLowerCase().contains(lowerQuery) ?? (object['description']?.toLowerCase().contains(lowerQuery) ??
false) || false) ||
(isActiveStatus.contains(lowerQuery)); (isActiveStatus.contains(lowerQuery));
}).toList(); }).toList();
currentPage = 0;
}); });
print("filteredDepartment: $filteredDepartment"); print("filteredDepartment: $filteredDepartment");
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFf5f5f5), backgroundColor: Color(0xFFf5f5f5),
@ -167,11 +175,14 @@ class DepartmentListState extends State<DepartmentList> {
appBar: CustomAppBar(isDesktop: isDesktop), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding:
isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height * vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
@ -184,7 +195,8 @@ class DepartmentListState extends State<DepartmentList> {
), ),
), ),
); );
}); },
);
} }
Widget buildGroupList(bool isDesktop) { Widget buildGroupList(bool isDesktop) {
@ -212,7 +224,8 @@ class DepartmentListState extends State<DepartmentList> {
// ? EdgeInsets.all(10.0) // ? EdgeInsets.all(10.0)
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10), // padding: const EdgeInsets.all(10),
height: isDesktop height:
isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
@ -244,9 +257,7 @@ class DepartmentListState extends State<DepartmentList> {
], ],
), ),
if (isDesktop) if (isDesktop)
SizedBox( SizedBox(width: MediaQuery.of(context).size.width * 0.16),
width: MediaQuery.of(context).size.width * 0.16,
),
if (isDesktop) if (isDesktop)
Container( Container(
@ -258,7 +269,9 @@ class DepartmentListState extends State<DepartmentList> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -270,17 +283,19 @@ class DepartmentListState extends State<DepartmentList> {
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, width: 0.5), color: Colors.grey.shade200,
width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
// SizedBox(width: 16), // SizedBox(width: 16),
@ -294,16 +309,18 @@ class DepartmentListState extends State<DepartmentList> {
disabledForegroundColor: Colors.white, disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
side: side: BorderSide(color: Color(0xFF114D8B), width: 2),
BorderSide(color: Color(0xFF114D8B), width: 2),
), ),
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 20, vertical: 12), horizontal: 20,
vertical: 12,
),
), ),
onPressed: () async { onPressed: () async {
showDialog( showDialog(
context: context, context: context,
builder: (context) => DepartmentData( builder:
(context) => DepartmentData(
isDesktop: isDesktop, isDesktop: isDesktop,
layoutColor: layoutColor!, layoutColor: layoutColor!,
fetchGetDepartment: refreshData, fetchGetDepartment: refreshData,
@ -335,10 +352,7 @@ class DepartmentListState extends State<DepartmentList> {
], ],
), ),
if (!isDesktop) if (!isDesktop) SizedBox(height: 5),
SizedBox(
height: 5,
),
isDesktop isDesktop
? SizedBox.shrink() ? SizedBox.shrink()
: Row( : Row(
@ -353,7 +367,9 @@ class DepartmentListState extends State<DepartmentList> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -366,17 +382,18 @@ class DepartmentListState extends State<DepartmentList> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, color: Colors.grey.shade200,
width: 0.5), width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
// SizedBox(width: 16), // SizedBox(width: 16),
@ -414,14 +431,17 @@ class DepartmentListState extends State<DepartmentList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.grey), color: Colors.grey,
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
"Please Create Department Details", "Please Create Department Details",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 16, color: Colors.grey), fontSize: 16,
color: Colors.grey,
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
], ],
@ -430,7 +450,8 @@ class DepartmentListState extends State<DepartmentList> {
); );
} }
/* Here collect the list to displayed the data in table or card Used */ /* Here collect the list to displayed the data in table or card Used */
List<dynamic> object = filteredDepartment.isNotEmpty List<dynamic> object =
filteredDepartment.isNotEmpty
? filteredDepartment ? filteredDepartment
: allDepartment; : allDepartment;
@ -439,12 +460,12 @@ class DepartmentListState extends State<DepartmentList> {
DateTime dateA = DateTime.parse(a['created_on']); DateTime dateA = DateTime.parse(a['created_on']);
DateTime dateB = DateTime.parse(b['created_on']); DateTime dateB = DateTime.parse(b['created_on']);
return dateB return dateB.compareTo(dateA); // Descending: newest first
.compareTo(dateA); // Descending: newest first
}); });
/* For pagination for list ... */ /* For pagination for list ... */
List paginatedDepartment = object List paginatedDepartment =
object
.skip(currentPage * itemsPerPage) .skip(currentPage * itemsPerPage)
.take(itemsPerPage) .take(itemsPerPage)
.toList(); .toList();
@ -452,8 +473,7 @@ class DepartmentListState extends State<DepartmentList> {
/* Table ... */ /* Table ... */
Widget table = LayoutBuilder( Widget table = LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
double minWidth = double minWidth = isDesktop ? constraints.maxWidth : 1300;
isDesktop ? constraints.maxWidth : 1300;
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints(minWidth: minWidth), constraints: BoxConstraints(minWidth: minWidth),
@ -462,7 +482,9 @@ class DepartmentListState extends State<DepartmentList> {
columnSpacing: isDesktop ? 24.0 : 16.0, columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder( border: TableBorder(
horizontalInside: BorderSide( horizontalInside: BorderSide(
width: 0.5, color: Colors.grey.shade200), width: 0.5,
color: Colors.grey.shade200,
),
), ),
columns: [ columns: [
DataColumn( DataColumn(
@ -470,51 +492,68 @@ class DepartmentListState extends State<DepartmentList> {
'Name', 'Name',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Description', 'Description',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Status', 'Status',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Actions', 'Actions',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
], ],
rows: paginatedDepartment.map((tableObject) { rows:
paginatedDepartment.map((tableObject) {
String departmentId = String departmentId =
tableObject['department_id'] tableObject['department_id']
.toString(); // Get user ID .toString(); // Get user ID
bool isSelected = bool isSelected =
selectedDepartmentId == departmentId; selectedDepartmentId == departmentId;
return DataRow(cells: [ return DataRow(
DataCell(Text(tableObject['name'] ?? '', cells: [
DataCell(
Text(
tableObject['name'] ?? '',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
),
),
DataCell( DataCell(
Text(tableObject['description'] ?? 'N/A', Text(
tableObject['description'] ?? 'N/A',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis,
),
),
DataCell( DataCell(
Text( Text(
tableObject['is_active'] == "1" tableObject['is_active'] == "1"
@ -523,9 +562,10 @@ class DepartmentListState extends State<DepartmentList> {
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
color: tableObject['is_active'] == "1" color:
tableObject['is_active'] == "1"
? Colors.green ? Colors.green
: Colors.red, : Colors.grey,
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -538,37 +578,45 @@ class DepartmentListState extends State<DepartmentList> {
// apiService.getSingleUser(id), // apiService.getSingleUser(id),
// ), // ),
GestureDetector( GestureDetector(
child: Tooltip(
message: 'Edit Department Details',
child: Image.asset( child: Image.asset(
'assets/images/IconsImg/edit.png', 'assets/images/IconsImg/edit.png',
width: 20, width: 20,
height: 15), height: 15,
),
),
onTap: () async { onTap: () async {
// final userId = getUserId(user['user_id']); // final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId); // final usersData = await getUserDetails(userId);
// //
final departmentId = int.tryParse( final departmentId = int.tryParse(
tableObject['department_id'] tableObject['department_id']
.toString()); .toString(),
);
if (departmentId != null) { if (departmentId != null) {
print( print(
"Table cell - department Id -- $departmentId"); "Table cell - department Id -- $departmentId",
);
final data = await apiService final data = await apiService
.getDepartmentDetailsFind( .getDepartmentDetailsFind(
departmentId); departmentId,
);
print("DepartmentId -- $data"); print("DepartmentId -- $data");
showDialog( showDialog(
context: context, context: context,
builder: (context) => builder:
DepartmentData( (context) => DepartmentData(
isDesktop: isDesktop, isDesktop: isDesktop,
departmentId: departmentId:
departmentId, // Pass the ID departmentId, // Pass the ID
departmentData: data, departmentData: data,
layoutColor: layoutColor!, layoutColor: layoutColor!,
// fetchGetForex: fetchGetForex, // fetchGetForex: fetchGetForex,
fetchGetDepartment: refreshData, fetchGetDepartment:
refreshData,
// role: // role:
// "Travel Agent" // "Travel Agent"
), ),
@ -579,7 +627,8 @@ class DepartmentListState extends State<DepartmentList> {
}, },
), ),
), ),
]); ],
);
}).toList(), }).toList(),
), ),
); );
@ -595,7 +644,9 @@ class DepartmentListState extends State<DepartmentList> {
return Card( return Card(
color: Colors.white, color: Colors.white,
margin: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(
horizontal: 12, vertical: 6), horizontal: 12,
vertical: 6,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@ -615,34 +666,42 @@ class DepartmentListState extends State<DepartmentList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87, color: Colors.black87,
fontWeight: FontWeight.w700), fontWeight: FontWeight.w700,
),
), ),
GestureDetector( GestureDetector(
child: Tooltip(
message: 'Edit Department Details',
child: Image.asset( child: Image.asset(
'assets/images/IconsImg/edit.png', 'assets/images/IconsImg/edit.png',
width: 20, width: 20,
height: 15), height: 15,
),
),
onTap: () async { onTap: () async {
// final userId = getUserId(user['user_id']); // final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId); // final usersData = await getUserDetails(userId);
// //
final departmentId = int.tryParse( final departmentId = int.tryParse(
cardObject['department_id'] cardObject['department_id']
.toString()); .toString(),
);
if (departmentId != null) { if (departmentId != null) {
print( print(
"departmentId -- $departmentId"); "departmentId -- $departmentId",
);
final data = await apiService final data = await apiService
.getDepartmentDetailsFind( .getDepartmentDetailsFind(
departmentId); departmentId,
);
print("DepartmentId -- $data"); print("DepartmentId -- $data");
showDialog( showDialog(
context: context, context: context,
builder: (context) => builder:
DepartmentData( (context) => DepartmentData(
isDesktop: isDesktop, isDesktop: isDesktop,
departmentId: departmentId:
departmentId, // Pass the ID departmentId, // Pass the ID
@ -749,13 +808,12 @@ class DepartmentListState extends State<DepartmentList> {
cardObject['description'] ?? '', cardObject['description'] ?? '',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
SizedBox( SizedBox(width: 10),
width: 10,
),
Column( Column(
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
@ -764,7 +822,8 @@ class DepartmentListState extends State<DepartmentList> {
cardObject['description'] ?? '', cardObject['description'] ?? '',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
@ -785,7 +844,8 @@ class DepartmentListState extends State<DepartmentList> {
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: isDesktop child:
isDesktop
? (searchController.text.isNotEmpty && ? (searchController.text.isNotEmpty &&
filteredDepartment.isEmpty filteredDepartment.isEmpty
? Center( ? Center(
@ -793,7 +853,8 @@ class DepartmentListState extends State<DepartmentList> {
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey), color: Colors.grey,
),
), ),
) )
: SingleChildScrollView( : SingleChildScrollView(
@ -807,11 +868,13 @@ class DepartmentListState extends State<DepartmentList> {
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey), color: Colors.grey,
),
), ),
) )
: buildMobileCardView( : buildMobileCardView(
paginatedDepartment)), paginatedDepartment,
)),
), ),
// Expanded( // Expanded(
// child: isDesktop // child: isDesktop
@ -842,9 +905,11 @@ class DepartmentListState extends State<DepartmentList> {
), ),
); );
}, },
) ),
]), ],
)), ),
),
),
); );
} }
} }

View File

@ -100,11 +100,13 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
} }
} else { } else {
throw Exception( throw Exception(
"Unexpected response format: Expected a List but got ${responseBody.runtimeType}"); "Unexpected response format: Expected a List but got ${responseBody.runtimeType}",
);
} }
} else { } else {
throw Exception( throw Exception(
'Failed to load users. Status Code: ${response.statusCode}'); 'Failed to load users. Status Code: ${response.statusCode}',
);
} }
} catch (e) { } catch (e) {
print("Error fetching users: $e"); print("Error fetching users: $e");
@ -138,7 +140,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
List<dynamic> travellerList = responseBody['data']; List<dynamic> travellerList = responseBody['data'];
setState(() { setState(() {
_traveller = travellerList _traveller =
travellerList
.map((user) => SearchTraveler.fromJson(user)) .map((user) => SearchTraveler.fromJson(user))
.toList(); .toList();
_filteredTraveller = List.from(_traveller); _filteredTraveller = List.from(_traveller);
@ -147,47 +150,53 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
print("Users fetched: ${_users.length}"); print("Users fetched: ${_users.length}");
for (var travvelr in _traveller) { for (var travvelr in _traveller) {
print( print(
"${travvelr.firstName} ${travvelr.lastName} ${travvelr.mobileNo}"); "${travvelr.firstName} ${travvelr.lastName} ${travvelr.mobileNo}",
);
} }
} else { } else {
throw Exception( throw Exception(
"Unexpected response format: Expected a List but got ${responseBody.runtimeType}"); "Unexpected response format: Expected a List but got ${responseBody.runtimeType}",
);
} }
} else { } else {
throw Exception( throw Exception(
'Failed to load users. Status Code: ${response.statusCode}'); 'Failed to load users. Status Code: ${response.statusCode}',
);
} }
} catch (e) { } catch (e) {
print("Error fetching traveller: $e"); print("Error fetching traveller: $e");
} }
} }
void _filterUsers1(String query) { // void _filterUsers1(String query) {
print("Filtering users..."); // print("Filtering users...");
setState(() { // setState(() {
if (query.isEmpty) { // if (query.isEmpty) {
_filteredUsers = List.from(_users); // _filteredUsers = List.from(_users);
} else { // } else {
_filteredUsers = _users.where((user) { // _filteredUsers =
List<String> searchFields = [ // _users.where((user) {
"${user.firstName} ${user.lastName}".toLowerCase(), // List<String> searchFields = [
user.email.toLowerCase() ?? "", // "${user.firstName} ${user.lastName}".toLowerCase(),
user.userId.toLowerCase() ?? "", // user.email.toLowerCase() ?? "",
user.mobileNo ?? "", // user.empCode?.toLowerCase() ?? "",
user.alternateMobileNo ?? "" // user.userId.toLowerCase() ?? "",
]; // user.mobileNo ?? "",
// user.alternateMobileNo ?? "",
return searchFields // ];
.any((field) => field.contains(query.toLowerCase())); //
}).toList(); // return searchFields.any(
} // (field) => field.contains(query.toLowerCase()),
}); // );
// }).toList();
print("Filtered Users:"); // }
for (var user in _filteredUsers) { // });
print("${user.firstName} ${user.lastName}"); //
} // print("Filtered Users:");
} // for (var user in _filteredUsers) {
// print("${user.firstName} ${user.lastName}");
// }
// }
void _filterUsers(String query) { void _filterUsers(String query) {
print("Filtering _filterUsersTravellers..."); print("Filtering _filterUsersTravellers...");
@ -200,7 +209,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
]; ];
} else { } else {
_filteredList = [ _filteredList = [
..._users.where((user) { ..._users
.where((user) {
print("usersLLL : ${user}"); print("usersLLL : ${user}");
List<String> searchFields = [ List<String> searchFields = [
@ -208,11 +218,14 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
user.email.toLowerCase() ?? "", user.email.toLowerCase() ?? "",
user.userId.toLowerCase() ?? "", user.userId.toLowerCase() ?? "",
user.mobileNo ?? "", user.mobileNo ?? "",
user.alternateMobileNo ?? "" user.alternateMobileNo ?? "",
user.empCode?.toLowerCase() ?? "",
]; ];
return searchFields return searchFields.any(
.any((field) => field.contains(query.toLowerCase())); (field) => field.contains(query.toLowerCase()),
}).map((user) => {"type": "user", "data": user}), );
})
.map((user) => {"type": "user", "data": user}),
]; ];
} }
}); });
@ -221,7 +234,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
for (var item in _filteredList) { for (var item in _filteredList) {
var user = item["data"]; var user = item["data"];
print( print(
"${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}"); "${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}",
);
} }
} }
@ -236,16 +250,19 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
]; ];
} else { } else {
_filteredList = [ _filteredList = [
..._traveller.where((traveller) { ..._traveller
.where((traveller) {
List<String> searchFields = [ List<String> searchFields = [
"${traveller.firstName} ${traveller.lastName}".toLowerCase(), "${traveller.firstName} ${traveller.lastName}".toLowerCase(),
traveller.email.toLowerCase() ?? "", traveller.email.toLowerCase() ?? "",
traveller.travellerId.toLowerCase() ?? "", traveller.travellerId.toLowerCase() ?? "",
traveller.mobileNo ?? "", traveller.mobileNo ?? "",
]; ];
return searchFields return searchFields.any(
.any((field) => field.contains(query.toLowerCase())); (field) => field.contains(query.toLowerCase()),
}).map((traveller) => {"type": "traveller", "data": traveller}), );
})
.map((traveller) => {"type": "traveller", "data": traveller}),
]; ];
} }
}); });
@ -254,7 +271,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
for (var item in _filteredList) { for (var item in _filteredList) {
var user = item["data"]; var user = item["data"];
print( print(
"${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}"); "${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}",
);
} }
} }
@ -266,32 +284,39 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
if (query.isEmpty) { if (query.isEmpty) {
_filteredList = [ _filteredList = [
..._users.map((user) => {"type": "user", "data": user}), ..._users.map((user) => {"type": "user", "data": user}),
..._traveller ..._traveller.map(
.map((traveller) => {"type": "traveller", "data": traveller}), (traveller) => {"type": "traveller", "data": traveller},
),
]; ];
} else { } else {
_filteredList = [ _filteredList = [
..._users.where((user) { ..._users
.where((user) {
List<String> searchFields = [ List<String> searchFields = [
"${user.firstName} ${user.lastName}".toLowerCase(), "${user.firstName} ${user.lastName}".toLowerCase(),
user.email.toLowerCase() ?? "", user.email.toLowerCase() ?? "",
user.userId.toLowerCase() ?? "", user.userId.toLowerCase() ?? "",
user.mobileNo ?? "", user.mobileNo ?? "",
user.alternateMobileNo ?? "" user.alternateMobileNo ?? "",
]; ];
return searchFields return searchFields.any(
.any((field) => field.contains(query.toLowerCase())); (field) => field.contains(query.toLowerCase()),
}).map((user) => {"type": "user", "data": user}), );
..._traveller.where((traveller) { })
.map((user) => {"type": "user", "data": user}),
..._traveller
.where((traveller) {
List<String> searchFields = [ List<String> searchFields = [
"${traveller.firstName} ${traveller.lastName}".toLowerCase(), "${traveller.firstName} ${traveller.lastName}".toLowerCase(),
traveller.email.toLowerCase() ?? "", traveller.email.toLowerCase() ?? "",
traveller.travellerId.toLowerCase() ?? "", traveller.travellerId.toLowerCase() ?? "",
traveller.mobileNo ?? "", traveller.mobileNo ?? "",
]; ];
return searchFields return searchFields.any(
.any((field) => field.contains(query.toLowerCase())); (field) => field.contains(query.toLowerCase()),
}).map((traveller) => {"type": "traveller", "data": traveller}), );
})
.map((traveller) => {"type": "traveller", "data": traveller}),
]; ];
} }
}); });
@ -300,7 +325,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
for (var item in _filteredList) { for (var item in _filteredList) {
var user = item["data"]; var user = item["data"];
print( print(
"${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}"); "${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}",
);
} }
} }
@ -324,10 +350,14 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
MainAxisSize.min, // Ensures content doesn't expand unnecessarily MainAxisSize.min, // Ensures content doesn't expand unnecessarily
children: [ children: [
widget.title == "Others" widget.title == "Others"
? Text("Please Select Other User", ? Text(
style: GoogleFonts.poppins(fontSize: 14)) "Please Select Other User",
: Text("Please Select Other Employee", style: GoogleFonts.poppins(fontSize: 14),
style: GoogleFonts.poppins(fontSize: 14)), )
: Text(
"Please Select Other Employee",
style: GoogleFonts.poppins(fontSize: 14),
),
SizedBox(height: 10), SizedBox(height: 10),
// Search Field // Search Field
@ -344,11 +374,14 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
style: GoogleFonts.poppins(fontSize: 12), style: GoogleFonts.poppins(fontSize: 12),
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search for a user", hintText: "Search for a user",
hintStyle: hintStyle: GoogleFonts.poppins(
GoogleFonts.poppins(fontSize: 14, color: Colors.grey), fontSize: 14,
color: Colors.grey,
),
prefixIcon: Icon(Icons.search), prefixIcon: Icon(Icons.search),
border: border: OutlineInputBorder(
OutlineInputBorder(borderRadius: BorderRadius.circular(8)), borderRadius: BorderRadius.circular(8),
),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey.shade200, width: 1), borderSide: BorderSide(color: Colors.grey.shade200, width: 1),
// borderSide: BorderSide(color: Color(0xFFF5F5F5), width: 2), // borderSide: BorderSide(color: Color(0xFFF5F5F5), width: 2),
@ -366,9 +399,13 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("or create a new traveler", Text(
"or create a new traveler",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, color: Color(0xFF575A74))), fontSize: 14,
color: Color(0xFF575A74),
),
),
TextButton( TextButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
@ -376,9 +413,13 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
_searchController.clear(); _searchController.clear();
}); });
}, },
child: Text("Create", child: Text(
"Create",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, color: widget.layoutColorForUser)), fontSize: 14,
color: widget.layoutColorForUser,
),
),
), ),
], ],
), ),
@ -391,12 +432,15 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
? SizedBox( ? SizedBox(
height: 300, // Limit height to avoid overflow height: 300, // Limit height to avoid overflow
// child: _filteredUsers.isEmpty // child: _filteredUsers.isEmpty
child: _filteredList.isEmpty child:
_filteredList.isEmpty
? Center( ? Center(
child: Text( child: Text(
"No users found", "No users found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey), fontSize: 14,
color: Colors.grey,
),
), ),
) )
: ListView.builder( : ListView.builder(
@ -411,7 +455,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
item["type"]; // "user" or "traveller" item["type"]; // "user" or "traveller"
if (user is Map<String, dynamic>) { if (user is Map<String, dynamic>) {
print( print(
"userLsirer - ${jsonEncode(user)}"); // pretty JSON-like string "userLsirer - ${jsonEncode(user)}",
); // pretty JSON-like string
} else { } else {
print("userLsirer - $user"); // fallback print("userLsirer - $user"); // fallback
} }
@ -420,32 +465,37 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
"${user.firstName ?? "Unknown"} ${user.lastName ?? ""}", "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}",
style: GoogleFonts.poppins(fontSize: 11), style: GoogleFonts.poppins(fontSize: 11),
), ),
subtitle: userType == "user" subtitle:
userType == "user"
? Text( ? Text(
"Employee ID: ${user.empCode ?? ""} ", "Employee ID: ${user.empCode ?? ""} ",
// "Employee ID: ${userType == "user" ? user.userId : user.travellerId}", // "Employee ID: ${userType == "user" ? user.userId : user.travellerId}",
style: style: GoogleFonts.poppins(
GoogleFonts.poppins(fontSize: 10), fontSize: 10,
),
) )
: Text( : Text(
"Mobile : ${user.mobileNo ?? ""} ", "Mobile : ${user.mobileNo ?? ""} ",
// "Employee ID: ${userType == "user" ? user.userId : user.travellerId}", // "Employee ID: ${userType == "user" ? user.userId : user.travellerId}",
style: style: GoogleFonts.poppins(
GoogleFonts.poppins(fontSize: 10), fontSize: 10,
),
), ),
onTap: () { onTap: () {
String selectedUser = String selectedUser =
"${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"; "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}";
setState(() { setState(() {
_searchController.text = selectedUser; _searchController.text = selectedUser;
userIdSelected = userType == "user" userIdSelected =
userType == "user"
? user.userId ? user.userId
: user.travellerId; : user.travellerId;
isTraveller = userType == "traveller"; isTraveller = userType == "traveller";
}); });
print( print(
"Selected: $selectedUser, ID: ${userType == "user" ? user.userId : user.travellerId}," "Selected: $selectedUser, ID: ${userType == "user" ? user.userId : user.travellerId},"
" isTraveller: $userIdSelected"); " isTraveller: $userIdSelected",
);
}, },
); );
}, },
@ -462,10 +512,16 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: TravelerForm( child: TravelerForm(
formKey: _formKey, formKey: _formKey,
onSubmit: (String fullName, String travellerId, onSubmit: (
bool isTraveller) { String fullName,
widget.onSubmit(fullName, travellerId, String travellerId,
isTraveller); // Pass the data up bool isTraveller,
) {
widget.onSubmit(
fullName,
travellerId,
isTraveller,
); // Pass the data up
}, },
firstNameController: TextEditingController(), firstNameController: TextEditingController(),
lastNameController: TextEditingController(), lastNameController: TextEditingController(),
@ -487,7 +543,9 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
side: BorderSide( side: BorderSide(
color: widget.layoutColorForUser, width: 2), color: widget.layoutColorForUser,
width: 2,
),
), ),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
@ -508,15 +566,21 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
side: BorderSide( side: BorderSide(
color: widget.layoutColorForUser, width: 2), color: widget.layoutColorForUser,
width: 2,
),
), ),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
onPressed: () { onPressed: () {
print( print(
"Submitting: ${_searchController.text}, ID: $userIdSelected"); "Submitting: ${_searchController.text}, ID: $userIdSelected",
);
widget.onSubmit( widget.onSubmit(
_searchController.text, userIdSelected, isTraveller); _searchController.text,
userIdSelected,
isTraveller,
);
Navigator.pop(context); Navigator.pop(context);
}, },
child: Text( child: Text(
@ -542,14 +606,15 @@ class TravelerForm extends StatefulWidget {
final GlobalKey<FormState> formKey; final GlobalKey<FormState> formKey;
final void Function(String, String, bool) onSubmit; final void Function(String, String, bool) onSubmit;
TravelerForm( TravelerForm({
{required this.formKey, required this.formKey,
required this.orgId, required this.orgId,
required this.firstNameController, required this.firstNameController,
required this.lastNameController, required this.lastNameController,
required this.emailController, required this.emailController,
required this.mobileController, required this.mobileController,
required this.onSubmit}); required this.onSubmit,
});
@override @override
_TravelerFormState createState() => _TravelerFormState(); _TravelerFormState createState() => _TravelerFormState();
@ -582,8 +647,9 @@ class _TravelerFormState extends State<TravelerForm> {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Email is required'; return 'Email is required';
} }
if (!RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$') if (!RegExp(
.hasMatch(value)) { r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
).hasMatch(value)) {
return 'Enter a valid email address'; return 'Enter a valid email address';
} }
return null; return null;
@ -642,7 +708,8 @@ class _TravelerFormState extends State<TravelerForm> {
String lastName = travellerData["last_name"]; String lastName = travellerData["last_name"];
print( print(
"Traveller Added: ID: $travellerId, Name: $firstName $lastName"); "Traveller Added: ID: $travellerId, Name: $firstName $lastName",
);
// // Pass data to callback // // Pass data to callback
// widget.onSubmit("$firstName $lastName", travellerId, true); // widget.onSubmit("$firstName $lastName", travellerId, true);
@ -658,21 +725,22 @@ class _TravelerFormState extends State<TravelerForm> {
SnackBar( SnackBar(
content: Text( content: Text(
"Traveller added successfully!", "Traveller added successfully!",
style: style: GoogleFonts.poppins(
GoogleFonts.poppins(color: Colors.white), // ✅ Set text color color: Colors.white,
), // ✅ Set text color
), ),
backgroundColor: Colors.green, backgroundColor: Colors.green,
), ),
); );
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text("Error: ${response.body}")), context,
); ).showSnackBar(SnackBar(content: Text("Error: ${response.body}")));
} }
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text("Failed to connect to server.")), context,
); ).showSnackBar(SnackBar(content: Text("Failed to connect to server.")));
} }
} }
@ -697,8 +765,10 @@ class _TravelerFormState extends State<TravelerForm> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text("Create Traveler", Text(
style: GoogleFonts.poppins(color: Colors.black54)), "Create Traveler",
style: GoogleFonts.poppins(color: Colors.black54),
),
SizedBox(height: 7), SizedBox(height: 7),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
@ -714,13 +784,17 @@ class _TravelerFormState extends State<TravelerForm> {
onPressed: () { onPressed: () {
widget.formKey.currentState?.reset(); widget.formKey.currentState?.reset();
}, },
child: Text("Clear", child: Text(
style: GoogleFonts.poppins(color: Colors.grey)), "Clear",
style: GoogleFonts.poppins(color: Colors.grey),
),
), ),
TextButton( TextButton(
onPressed: () => _onSubmit(context), onPressed: () => _onSubmit(context),
child: Text("Add", child: Text(
style: GoogleFonts.poppins(color: Color(0xFF114D8B))), "Add",
style: GoogleFonts.poppins(color: Color(0xFF114D8B)),
),
), ),
], ],
), ),

View File

@ -21,13 +21,14 @@ class ForexData extends StatefulWidget {
final int? forexId; // <-- Add this final int? forexId; // <-- Add this
final Map<String, dynamic>? forexData; final Map<String, dynamic>? forexData;
const ForexData( const ForexData({
{super.key, super.key,
required this.isDesktop, required this.isDesktop,
this.layoutColor, this.layoutColor,
required this.fetchGetForex, required this.fetchGetForex,
this.forexId, this.forexId,
this.forexData}); this.forexData,
});
@override @override
ForexDataState createState() => ForexDataState(); ForexDataState createState() => ForexDataState();
@ -61,7 +62,7 @@ class ForexDataState extends State<ForexData> {
"currency", "currency",
"perdiemAmount", "perdiemAmount",
"cash", "cash",
"card" "card",
]; ];
Map<String, dynamic> forex_Detials() { Map<String, dynamic> forex_Detials() {
@ -197,7 +198,7 @@ class ForexDataState extends State<ForexData> {
"currency", "currency",
"perdiemAmount", "perdiemAmount",
"cash_percentage", "cash_percentage",
"card_percentage" "card_percentage",
]; ];
// Check validation for each field // Check validation for each field
@ -273,7 +274,8 @@ class ForexDataState extends State<ForexData> {
}; };
final body = jsonEncode(forexData); final body = jsonEncode(forexData);
final response = forexDataId != null final response =
forexDataId != null
? await http.put(uri, headers: headers, body: body) ? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body); : await http.post(uri, headers: headers, body: body);
@ -326,7 +328,7 @@ class ForexDataState extends State<ForexData> {
// Map country codes to country names // Map country codes to country names
countryMap = { countryMap = {
for (var item in countryList) for (var item in countryList)
item['country_code'] as String: item['country_name'] as String item['country_code'] as String: item['country_name'] as String,
}; };
// Extract only country codes for processing // Extract only country codes for processing
@ -355,21 +357,19 @@ class ForexDataState extends State<ForexData> {
], ],
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Divider( Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
const SizedBox(height: 5), const SizedBox(height: 5),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Country", "Country *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -381,13 +381,14 @@ class ForexDataState extends State<ForexData> {
selectedItem: countryMap[selectedCountry], selectedItem: countryMap[selectedCountry],
popupProps: PopupProps.menu( popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality showSearchBox: true, // Enables search functionality
menuProps: const MenuProps( menuProps: const MenuProps(backgroundColor: Colors.white),
backgroundColor: Colors.white,
),
constraints: BoxConstraints(maxHeight: 250), constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, item, isSelected) => Padding( itemBuilder:
(context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0), horizontal: 8.0,
vertical: 6.0,
),
child: Text( child: Text(
item, item,
style: GoogleFonts.poppins(fontSize: 11.5), style: GoogleFonts.poppins(fontSize: 11.5),
@ -405,12 +406,11 @@ class ForexDataState extends State<ForexData> {
dropdownDecoratorProps: DropDownDecoratorProps( dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration( dropdownSearchDecoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(horizontal: 1),
horizontal: 1,
), ),
), ),
), dropdownBuilder:
dropdownBuilder: (context, selectedItem) => Align( (context, selectedItem) => Align(
// Center-align selected item // Center-align selected item
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
@ -421,7 +421,8 @@ class ForexDataState extends State<ForexData> {
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() {
// Find the country_code based on selected country_name // Find the country_code based on selected country_name
selectedCountry = countryMap.entries selectedCountry =
countryMap.entries
.firstWhere((entry) => entry.value == newValue) .firstWhere((entry) => entry.value == newValue)
.key; .key;
selectedCountryName = newValue; selectedCountryName = newValue;
@ -444,11 +445,12 @@ class ForexDataState extends State<ForexData> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Currency", "Currency *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -470,7 +472,8 @@ class ForexDataState extends State<ForexData> {
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), ),
),
), ),
if (errorMessages["currency"] != null) ...[ if (errorMessages["currency"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -481,11 +484,9 @@ class ForexDataState extends State<ForexData> {
], ],
], ],
), ),
// if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
SizedBox( // if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
height: 10, SizedBox(height: 10),
),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -493,18 +494,20 @@ class ForexDataState extends State<ForexData> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Cash (%)", "Cash (%) *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
isFocused: false, isFocused: false,
isDesktop: widget.isDesktop, isDesktop: widget.isDesktop,
color: Colors.transparent, color: Colors.transparent,
width: widget.isDesktop width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.09 ? MediaQuery.of(context).size.width * 0.09
: MediaQuery.of(context).size.width * 0.66, : MediaQuery.of(context).size.width * 0.66,
child: SizedBox( child: SizedBox(
@ -518,13 +521,16 @@ class ForexDataState extends State<ForexData> {
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "Cash", labelText: "Cash",
labelStyle: labelStyle: TextStyle(
TextStyle(fontSize: 11, color: Colors.grey), fontSize: 11,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), ),
),
), ),
if (errorMessages["cash_percentage"] != null) ...[ if (errorMessages["cash_percentage"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -540,18 +546,20 @@ class ForexDataState extends State<ForexData> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Card (%)", "Card (%) *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
isFocused: false, isFocused: false,
isDesktop: widget.isDesktop, isDesktop: widget.isDesktop,
color: Colors.transparent, color: Colors.transparent,
width: widget.isDesktop width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.09 ? MediaQuery.of(context).size.width * 0.09
: MediaQuery.of(context).size.width * 0.66, : MediaQuery.of(context).size.width * 0.66,
child: SizedBox( child: SizedBox(
@ -565,13 +573,16 @@ class ForexDataState extends State<ForexData> {
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "Card", labelText: "Card",
labelStyle: labelStyle: TextStyle(
TextStyle(fontSize: 11, color: Colors.grey), fontSize: 11,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), ),
),
), ),
if (errorMessages["card_percentage"] != null) ...[ if (errorMessages["card_percentage"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -581,21 +592,20 @@ class ForexDataState extends State<ForexData> {
), ),
], ],
], ],
) ),
], ],
), ),
SizedBox( SizedBox(height: 10),
height: 10,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Perdiem Amount", "Perdiem Amount *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -614,7 +624,8 @@ class ForexDataState extends State<ForexData> {
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), ),
),
), ),
if (errorMessages["perdiemAmount"] != null) ...[ if (errorMessages["perdiemAmount"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -625,9 +636,7 @@ class ForexDataState extends State<ForexData> {
], ],
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
if (forexDataId != null) if (forexDataId != null)
Row( Row(
@ -638,7 +647,8 @@ class ForexDataState extends State<ForexData> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
Tooltip( Tooltip(
message: message:
@ -654,13 +664,10 @@ class ForexDataState extends State<ForexData> {
), ),
), ),
), ),
) ),
], ],
), ),
if (forexDataId != null) if (forexDataId != null) SizedBox(height: 15),
SizedBox(
height: 15,
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
@ -697,13 +704,17 @@ class ForexDataState extends State<ForexData> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
), ),
child: Text('Save', child: Text(
'Save',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)), fontSize: 11,
color: Colors.white,
),
),
), ),
), ),
], ],
) ),
// : SizedBox.shrink(), // : SizedBox.shrink(),
], ],
), ),

View File

@ -83,11 +83,13 @@ class ForexDataListState extends State<ForexDataList> {
String? bodyStringColor = await getBodyColor(); String? bodyStringColor = await getBodyColor();
setState(() { setState(() {
layoutColor = layoutString != null layoutColor =
layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyStringColor != null bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
@ -100,7 +102,7 @@ class ForexDataListState extends State<ForexDataList> {
Future<List<dynamic>> fetchGetForex() async { Future<List<dynamic>> fetchGetForex() async {
orgId = await getOrgId(); orgId = await getOrgId();
final String apiUrlData = '$apiUrl/api/getForexPerdiemList'; final String apiUrlData = '$apiUrl/api/getForexPerdiemList?for=table_view';
final String? token = await getToken(); final String? token = await getToken();
@ -151,7 +153,8 @@ class ForexDataListState extends State<ForexDataList> {
if (!data.containsKey('data') || data['data'] is! List) { if (!data.containsKey('data') || data['data'] is! List) {
throw Exception( throw Exception(
"Invalid response format: 'data' field is missing or not a List"); "Invalid response format: 'data' field is missing or not a List",
);
} }
List<dynamic> plansJson = data['data']; // 'data' is a Map, not a List List<dynamic> plansJson = data['data']; // 'data' is a Map, not a List
@ -180,7 +183,10 @@ class ForexDataListState extends State<ForexDataList> {
} }
Future<void> createUserData( Future<void> createUserData(
Map<String, dynamic> userData, String userId, String newStatus) async { Map<String, dynamic> userData,
String userId,
String newStatus,
) async {
final uri = Uri.parse('$apiUrl/api/users/update/$userId'); final uri = Uri.parse('$apiUrl/api/users/update/$userId');
final String? token = await getToken(); final String? token = await getToken();
@ -230,8 +236,11 @@ class ForexDataListState extends State<ForexDataList> {
} }
} }
void handleToggleUserStatus(String userId, String currentStatus, void handleToggleUserStatus(
Map<String, dynamic> userData) async { String userId,
String currentStatus,
Map<String, dynamic> userData,
) async {
print("Toggling user status - $userId (Current: $currentStatus)"); print("Toggling user status - $userId (Current: $currentStatus)");
final String apiUrlData = final String apiUrlData =
@ -274,7 +283,7 @@ class ForexDataListState extends State<ForexDataList> {
// } // }
} }
// Refresh user list after update // Refresh user list after update
void refreshUserList() { void refreshUserList() {
setState(() { setState(() {
futureForex = fetchGetForex(); // Re-fetch users after status update futureForex = fetchGetForex(); // Re-fetch users after status update
@ -282,47 +291,35 @@ class ForexDataListState extends State<ForexDataList> {
}); });
} }
void filterForex1(String query) {
print("allUsers before filtering: $query");
final lowerQuery = query.toLowerCase();
setState(() {
filteredForex = allForex.where((forex) {
return (forex['country_code']?.toLowerCase().contains(lowerQuery) ??
false) ||
(forex['country_name']?.toLowerCase().contains(lowerQuery) ??
false) ||
(forex['currency']?.toLowerCase().contains(lowerQuery) ?? false) ||
(forex['perdiem_amount']?.toLowerCase().contains(lowerQuery) ??
false);
}).toList();
});
print("filteredPlans: $filteredForex");
}
void filterForex(String query) { void filterForex(String query) {
print("allForex before filtering: $query"); print("allForex before filtering: $query");
final lowerQuery = query.toLowerCase(); final lowerQuery = query.toLowerCase();
setState(() { setState(() {
filteredForex = allForex.where((forex) { filteredForex =
allForex.where((forex) {
final isActiveStatus = final isActiveStatus =
forex['is_active'] == "1" ? "active" : "inactive"; forex['is_active'] == "1" ? "active" : "inactive";
return (forex['country_code']?.toLowerCase().contains(lowerQuery) ?? return (forex['country_code']?.toLowerCase().contains(lowerQuery) ??
false) || false) ||
(forex['country_name']?.toLowerCase().contains(lowerQuery) ?? (forex['country_name']?.toLowerCase().contains(lowerQuery) ??
false) || false) ||
(forex['currency']?.toLowerCase().contains(lowerQuery) ?? false) || (forex['currency']?.toLowerCase().contains(lowerQuery) ??
false) ||
(forex['perdiem_amount']?.toLowerCase().contains(lowerQuery) ?? (forex['perdiem_amount']?.toLowerCase().contains(lowerQuery) ??
false) || false) ||
(isActiveStatus.contains(lowerQuery)); (isActiveStatus.contains(lowerQuery));
}).toList(); }).toList();
currentPage = 0;
}); });
print("filteredForex: $filteredForex"); print("filteredForex: $filteredForex");
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFf5f5f5), backgroundColor: Color(0xFFf5f5f5),
@ -331,11 +328,14 @@ class ForexDataListState extends State<ForexDataList> {
appBar: CustomAppBar(isDesktop: isDesktop), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding:
isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height * vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
@ -348,7 +348,8 @@ class ForexDataListState extends State<ForexDataList> {
), ),
), ),
); );
}); },
);
} }
Widget buildGroupList(bool isDesktop) { Widget buildGroupList(bool isDesktop) {
@ -376,7 +377,8 @@ class ForexDataListState extends State<ForexDataList> {
// ? EdgeInsets.all(10.0) // ? EdgeInsets.all(10.0)
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10), // padding: const EdgeInsets.all(10),
height: isDesktop height:
isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
@ -408,9 +410,7 @@ class ForexDataListState extends State<ForexDataList> {
], ],
), ),
if (isDesktop) if (isDesktop)
SizedBox( SizedBox(width: MediaQuery.of(context).size.width * 0.16),
width: MediaQuery.of(context).size.width * 0.16,
),
if (isDesktop) if (isDesktop)
Container( Container(
@ -422,7 +422,9 @@ class ForexDataListState extends State<ForexDataList> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -434,17 +436,19 @@ class ForexDataListState extends State<ForexDataList> {
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, width: 0.5), color: Colors.grey.shade200,
width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
// SizedBox(width: 16), // SizedBox(width: 16),
@ -458,16 +462,18 @@ class ForexDataListState extends State<ForexDataList> {
disabledForegroundColor: Colors.white, disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
side: side: BorderSide(color: Color(0xFF114D8B), width: 2),
BorderSide(color: Color(0xFF114D8B), width: 2),
), ),
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 20, vertical: 12), horizontal: 20,
vertical: 12,
),
), ),
onPressed: () async { onPressed: () async {
showDialog( showDialog(
context: context, context: context,
builder: (context) => ForexData( builder:
(context) => ForexData(
isDesktop: isDesktop, isDesktop: isDesktop,
layoutColor: layoutColor!, layoutColor: layoutColor!,
fetchGetForex: refreshData, fetchGetForex: refreshData,
@ -498,10 +504,7 @@ class ForexDataListState extends State<ForexDataList> {
], ],
), ),
if (!isDesktop) if (!isDesktop) SizedBox(height: 5),
SizedBox(
height: 5,
),
isDesktop isDesktop
? SizedBox.shrink() ? SizedBox.shrink()
: Row( : Row(
@ -516,7 +519,9 @@ class ForexDataListState extends State<ForexDataList> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -529,17 +534,18 @@ class ForexDataListState extends State<ForexDataList> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, color: Colors.grey.shade200,
width: 0.5), width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
// SizedBox(width: 16), // SizedBox(width: 16),
@ -577,14 +583,17 @@ class ForexDataListState extends State<ForexDataList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.grey), color: Colors.grey,
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
"Please Create Perdiem Amount", "Please Create Perdiem Amount",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 16, color: Colors.grey), fontSize: 16,
color: Colors.grey,
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
], ],
@ -600,19 +609,18 @@ class ForexDataListState extends State<ForexDataList> {
DateTime dateA = DateTime.parse(a['created_on']); DateTime dateA = DateTime.parse(a['created_on']);
DateTime dateB = DateTime.parse(b['created_on']); DateTime dateB = DateTime.parse(b['created_on']);
return dateB return dateB.compareTo(dateA); // Descending: newest first
.compareTo(dateA); // Descending: newest first
}); });
List paginatedForex = forex List paginatedForex =
forex
.skip(currentPage * itemsPerPage) .skip(currentPage * itemsPerPage)
.take(itemsPerPage) .take(itemsPerPage)
.toList(); .toList();
Widget table = LayoutBuilder( Widget table = LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
double minWidth = double minWidth = isDesktop ? constraints.maxWidth : 1300;
isDesktop ? constraints.maxWidth : 1300;
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints(minWidth: minWidth), constraints: BoxConstraints(minWidth: minWidth),
@ -621,7 +629,9 @@ class ForexDataListState extends State<ForexDataList> {
columnSpacing: isDesktop ? 24.0 : 16.0, columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder( border: TableBorder(
horizontalInside: BorderSide( horizontalInside: BorderSide(
width: 0.5, color: Colors.grey.shade200), width: 0.5,
color: Colors.grey.shade200,
),
), ),
columns: [ columns: [
DataColumn( DataColumn(
@ -629,76 +639,105 @@ class ForexDataListState extends State<ForexDataList> {
'Country Code', 'Country Code',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Country', 'Country',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Currency', 'Currency',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Perdiem Amount', 'Perdiem Amount',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Status', 'Status',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Actions', 'Actions',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
], ],
rows: paginatedForex.map((forex) { rows:
String forexId = forex['forex_perdiem_id'] paginatedForex.map((forex) {
String forexId =
forex['forex_perdiem_id']
.toString(); // Get user ID .toString(); // Get user ID
bool isSelected = selectedUserId == forexId; bool isSelected = selectedUserId == forexId;
return DataRow(cells: [ return DataRow(
cells: [
DataCell( DataCell(
Text("${forex['country_code'] ?? ''}", Text(
"${forex['country_code'] ?? ''}",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text(forex['country_name'] ?? '', ),
),
DataCell(
Text(
forex['country_name'] ?? '',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text(forex['currency'] ?? 'N/A', ),
),
DataCell(
Text(
forex['currency'] ?? 'N/A',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis,
),
),
DataCell( DataCell(
Text(forex['perdiem_amount'] ?? 'N/A', Text(
forex['perdiem_amount'] ?? 'N/A',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis,
),
),
DataCell( DataCell(
Text( Text(
forex['is_active'] == "1" forex['is_active'] == "1"
@ -722,17 +761,22 @@ class ForexDataListState extends State<ForexDataList> {
// apiService.getSingleUser(id), // apiService.getSingleUser(id),
// ), // ),
GestureDetector( GestureDetector(
child: Tooltip(
message: 'Edit ForEx Details',
child: Image.asset( child: Image.asset(
'assets/images/IconsImg/edit.png', 'assets/images/IconsImg/edit.png',
width: 20, width: 20,
height: 15), height: 15,
),
),
onTap: () async { onTap: () async {
// final userId = getUserId(user['user_id']); // final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId); // final usersData = await getUserDetails(userId);
// //
final forexId = int.tryParse( final forexId = int.tryParse(
forex['forex_perdiem_id'] forex['forex_perdiem_id']
.toString()); .toString(),
);
if (forexId != null) { if (forexId != null) {
print("ForexId -- $forexId"); print("ForexId -- $forexId");
@ -742,9 +786,11 @@ class ForexDataListState extends State<ForexDataList> {
showDialog( showDialog(
context: context, context: context,
builder: (context) => ForexData( builder:
(context) => ForexData(
isDesktop: isDesktop, isDesktop: isDesktop,
forexId: forexId, // Pass the ID forexId:
forexId, // Pass the ID
forexData: data, forexData: data,
layoutColor: layoutColor!, layoutColor: layoutColor!,
// fetchGetForex: fetchGetForex, // fetchGetForex: fetchGetForex,
@ -759,7 +805,8 @@ class ForexDataListState extends State<ForexDataList> {
}, },
), ),
), ),
]); ],
);
}).toList(), }).toList(),
), ),
); );
@ -774,7 +821,9 @@ class ForexDataListState extends State<ForexDataList> {
return Card( return Card(
color: Colors.white, color: Colors.white,
margin: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(
horizontal: 12, vertical: 6), horizontal: 12,
vertical: 6,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@ -794,21 +843,26 @@ class ForexDataListState extends State<ForexDataList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87, color: Colors.black87,
fontWeight: FontWeight.w700), fontWeight: FontWeight.w700,
),
), ),
GestureDetector( GestureDetector(
child: Tooltip(
message: 'Edit Forex Details',
child: Image.asset( child: Image.asset(
'assets/images/IconsImg/edit.png', 'assets/images/IconsImg/edit.png',
width: 20, width: 20,
height: 15), height: 15,
),
),
onTap: () async { onTap: () async {
// final userId = getUserId(user['user_id']); // final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId); // final usersData = await getUserDetails(userId);
// //
final forexId = int.tryParse( final forexId = int.tryParse(
forex['forex_perdiem_id'] forex['forex_perdiem_id'].toString(),
.toString()); );
if (forexId != null) { if (forexId != null) {
print("ForexId -- $forexId"); print("ForexId -- $forexId");
@ -818,7 +872,8 @@ class ForexDataListState extends State<ForexDataList> {
showDialog( showDialog(
context: context, context: context,
builder: (context) => ForexData( builder:
(context) => ForexData(
isDesktop: isDesktop, isDesktop: isDesktop,
forexId: forexId:
forexId, // Pass the ID forexId, // Pass the ID
@ -924,7 +979,8 @@ class ForexDataListState extends State<ForexDataList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
color: Colors.black87, color: Colors.black87,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w500,
),
), ),
], ],
), ),
@ -943,13 +999,12 @@ class ForexDataListState extends State<ForexDataList> {
forex['currency'] ?? '', forex['currency'] ?? '',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
SizedBox( SizedBox(width: 10),
width: 10,
),
Column( Column(
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
@ -958,7 +1013,8 @@ class ForexDataListState extends State<ForexDataList> {
forex['perdiem_amount'] ?? '', forex['perdiem_amount'] ?? '',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
@ -979,7 +1035,8 @@ class ForexDataListState extends State<ForexDataList> {
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: isDesktop child:
isDesktop
? (searchController.text.isNotEmpty && ? (searchController.text.isNotEmpty &&
filteredForex.isEmpty filteredForex.isEmpty
? Center( ? Center(
@ -987,7 +1044,8 @@ class ForexDataListState extends State<ForexDataList> {
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey), color: Colors.grey,
),
), ),
) )
: SingleChildScrollView( : SingleChildScrollView(
@ -1001,7 +1059,8 @@ class ForexDataListState extends State<ForexDataList> {
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey), color: Colors.grey,
),
), ),
) )
: buildMobileCardView(paginatedForex)), : buildMobileCardView(paginatedForex)),
@ -1035,9 +1094,11 @@ class ForexDataListState extends State<ForexDataList> {
), ),
); );
}, },
) ),
]), ],
)), ),
),
),
); );
} }
} }

View File

@ -21,13 +21,14 @@ class GroupData extends StatefulWidget {
final int? groupId; // <-- Add this final int? groupId; // <-- Add this
final Map<String, dynamic>? groupData; final Map<String, dynamic>? groupData;
const GroupData( const GroupData({
{super.key, super.key,
required this.isDesktop, required this.isDesktop,
this.layoutColor, this.layoutColor,
required this.fetchGetGroup, required this.fetchGetGroup,
this.groupId, this.groupId,
this.groupData}); this.groupData,
});
@override @override
GroupDataState createState() => GroupDataState(); GroupDataState createState() => GroupDataState();
@ -46,7 +47,6 @@ class GroupDataState extends State<GroupData> {
final Map<String, TextEditingController> controllers = {}; final Map<String, TextEditingController> controllers = {};
Map<String, String> errorMessages = {}; Map<String, String> errorMessages = {};
List<dynamic> domesticList = []; List<dynamic> domesticList = [];
List<dynamic> internationalList = []; List<dynamic> internationalList = [];
@ -75,12 +75,12 @@ class GroupDataState extends State<GroupData> {
Map<String, dynamic> group_Detials() { Map<String, dynamic> group_Detials() {
final data = { final data = {
"name":controllers["name"]?.text, "name": controllers["name"]?.text,
"description":controllers["description"]?.text, "description": controllers["description"]?.text,
"domestic_policy_id":selectedDomesticPolicyID, "domestic_policy_id": selectedDomesticPolicyID,
"international_policy_id":selectedInternationalPolicyID, "international_policy_id": selectedInternationalPolicyID,
"domestic_policy_name":selectedDomesticPolicyName, "domestic_policy_name": selectedDomesticPolicyName,
"international_policy_name":selectedInternationalPolicyName, "international_policy_name": selectedInternationalPolicyName,
"is_active": isActive, "is_active": isActive,
}; };
return data; return data;
@ -166,23 +166,19 @@ class GroupDataState extends State<GroupData> {
}); });
} }
bool validateData() { bool validateData() {
errorMessages.clear(); errorMessages.clear();
final data = { final data = {
"name":controllers["name"]?.text, "name": controllers["name"]?.text,
"description":controllers["description"]?.text, "description": controllers["description"]?.text,
"domestic_policy_id":selectedDomesticPolicyID, "domestic_policy_id": selectedDomesticPolicyID,
"international_policy_id":selectedInternationalPolicyID, "international_policy_id": selectedInternationalPolicyID,
"domestic_policy_name":selectedDomesticPolicyName, "domestic_policy_name": selectedDomesticPolicyName,
"international_policy_name":selectedInternationalPolicyName, "international_policy_name": selectedInternationalPolicyName,
}; };
final requiredFields = [ final requiredFields = ["name", "description"];
"name",
"description",
];
// Check validation for each field // Check validation for each field
for (String field in requiredFields) { for (String field in requiredFields) {
@ -240,7 +236,8 @@ class GroupDataState extends State<GroupData> {
}; };
final body = jsonEncode(groupData); final body = jsonEncode(groupData);
final response = groupDataId != null final response =
groupDataId != null
? await http.put(uri, headers: headers, body: body) ? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body); : await http.post(uri, headers: headers, body: body);
@ -288,7 +285,7 @@ class GroupDataState extends State<GroupData> {
// Map id to names // Map id to names
DomesticMap = { DomesticMap = {
for (var object in domesticList) for (var object in domesticList)
object['policy_id'] as String: object['name'] as String object['policy_id'] as String: object['name'] as String,
}; };
// print("domestic -- map--$DomesticMap"); // print("domestic -- map--$DomesticMap");
@ -302,7 +299,7 @@ class GroupDataState extends State<GroupData> {
InternationalMap = { InternationalMap = {
for (var item in internationalList) for (var item in internationalList)
item['policy_id'] as String: item['name'] as String item['policy_id'] as String: item['name'] as String,
}; };
// Extract only id for processing // Extract only id for processing
@ -330,20 +327,18 @@ class GroupDataState extends State<GroupData> {
], ],
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Divider( Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
const SizedBox(height: 5), const SizedBox(height: 5),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Name", "Name *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -362,7 +357,8 @@ class GroupDataState extends State<GroupData> {
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), ),
),
), ),
if (errorMessages["name"] != null) ...[ if (errorMessages["name"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -382,7 +378,8 @@ class GroupDataState extends State<GroupData> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -391,16 +388,18 @@ class GroupDataState extends State<GroupData> {
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: DropdownSearch<String>( child: DropdownSearch<String>(
selectedItem: InternationalMap[selectedInternationalPolicyID], selectedItem:
InternationalMap[selectedInternationalPolicyID],
popupProps: PopupProps.menu( popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality showSearchBox: true, // Enables search functionality
menuProps: const MenuProps( menuProps: const MenuProps(backgroundColor: Colors.white),
backgroundColor: Colors.white,
),
constraints: BoxConstraints(maxHeight: 250), constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, item, isSelected) => Padding( itemBuilder:
(context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0), horizontal: 8.0,
vertical: 6.0,
),
child: Text( child: Text(
item, item,
style: GoogleFonts.poppins(fontSize: 11.5), style: GoogleFonts.poppins(fontSize: 11.5),
@ -418,12 +417,11 @@ class GroupDataState extends State<GroupData> {
dropdownDecoratorProps: DropDownDecoratorProps( dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration( dropdownSearchDecoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(horizontal: 1),
horizontal: 1,
), ),
), ),
), dropdownBuilder:
dropdownBuilder: (context, selectedItem) => Align( (context, selectedItem) => Align(
// Center-align selected item // Center-align selected item
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
@ -434,7 +432,8 @@ class GroupDataState extends State<GroupData> {
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() {
// Find the country_code based on selected country_name // Find the country_code based on selected country_name
selectedInternationalPolicyID = InternationalMap.entries selectedInternationalPolicyID =
InternationalMap.entries
.firstWhere((entry) => entry.value == newValue) .firstWhere((entry) => entry.value == newValue)
.key; .key;
selectedInternationalPolicyName = newValue; selectedInternationalPolicyName = newValue;
@ -454,7 +453,8 @@ class GroupDataState extends State<GroupData> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -466,13 +466,14 @@ class GroupDataState extends State<GroupData> {
selectedItem: DomesticMap[selectedDomesticPolicyID], selectedItem: DomesticMap[selectedDomesticPolicyID],
popupProps: PopupProps.menu( popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality showSearchBox: true, // Enables search functionality
menuProps: const MenuProps( menuProps: const MenuProps(backgroundColor: Colors.white),
backgroundColor: Colors.white,
),
constraints: BoxConstraints(maxHeight: 250), constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, object, isSelected) => Padding( itemBuilder:
(context, object, isSelected) => Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0), horizontal: 8.0,
vertical: 6.0,
),
child: Text( child: Text(
object, object,
style: GoogleFonts.poppins(fontSize: 11.5), style: GoogleFonts.poppins(fontSize: 11.5),
@ -490,12 +491,11 @@ class GroupDataState extends State<GroupData> {
dropdownDecoratorProps: DropDownDecoratorProps( dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration( dropdownSearchDecoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(horizontal: 1),
horizontal: 1,
), ),
), ),
), dropdownBuilder:
dropdownBuilder: (context, selectedItem) => Align( (context, selectedItem) => Align(
// Center-align selected item // Center-align selected item
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
@ -506,7 +506,8 @@ class GroupDataState extends State<GroupData> {
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() {
// Find the country_code based on selected country_name // Find the country_code based on selected country_name
selectedDomesticPolicyID = DomesticMap.entries selectedDomesticPolicyID =
DomesticMap.entries
.firstWhere((entry) => entry.value == newValue) .firstWhere((entry) => entry.value == newValue)
.key; .key;
selectedDomesticPolicyName = newValue; selectedDomesticPolicyName = newValue;
@ -522,11 +523,12 @@ class GroupDataState extends State<GroupData> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Description", "Description *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -559,9 +561,7 @@ class GroupDataState extends State<GroupData> {
], ],
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
if (groupDataId != null) if (groupDataId != null)
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -571,7 +571,8 @@ class GroupDataState extends State<GroupData> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
Tooltip( Tooltip(
message: message:
@ -587,13 +588,10 @@ class GroupDataState extends State<GroupData> {
), ),
), ),
), ),
) ),
], ],
), ),
if (groupDataId != null) if (groupDataId != null) SizedBox(height: 15),
SizedBox(
height: 15,
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
@ -630,13 +628,17 @@ class GroupDataState extends State<GroupData> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
), ),
child: Text('Save', child: Text(
'Save',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)), fontSize: 11,
color: Colors.white,
),
),
), ),
), ),
], ],
) ),
// : SizedBox.shrink(), // : SizedBox.shrink(),
], ],
), ),

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,433 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:frontend/Screens/group/group.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:responsive_builder/responsive_builder.dart';
import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import 'groupDetails.dart';
class GroupListBackUp extends StatefulWidget {
@override
_GroupListBackUpState createState() => _GroupListBackUpState();
}
class _GroupListBackUpState extends State<GroupListBackUp> {
final ApiService apiService = ApiService();
List<dynamic>? apiAllGroups;
Color? layoutColor;
Color? bodyColor;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
loadAllGroups();
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;
});
}
Future<void> loadAllGroups() async {
try {
final result = await apiService.fetchAllGroup();
setState(() {
apiAllGroups = result;
});
print("Fetched services: $apiAllGroups");
} catch (e) {
print('Error fetching role list: $e');
}
}
void handleActiveStatus(
Map<String, dynamic> groupData,
String groupId,
String currentStatus,
) async {
print("Toggling user status - $groupId (Current: $currentStatus)");
final String apiUrlData =
'$apiUrl/api/groups/update/$groupId'; // API for updating user
final String? token = await getToken();
if (token == null) {
print("Error: Token not found");
return;
}
// Toggle status: If active ("1"), set to inactive ("0"); otherwise, activate ("1")
String newStatus = (currentStatus == "1") ? "0" : "1";
print("STatus 1 - $newStatus");
try {
final response = await http.put(
Uri.parse(apiUrlData),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode({
"is_active": newStatus // Set new status dynamically
}),
);
if (response.statusCode == 200 || response.statusCode == 201) {
print("User status updated successfully to $newStatus!");
loadAllGroups(); // Refresh users list after update
} else {
print("Failed to update user status. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print("Error updating user status: $e");
}
}
void deleteGroup(Map<String, dynamic> groupdata, groupId, status) {
print("GroupId : $groupId");
print("Groupstatus: $status");
print("GroupsData: $groupdata");
// handleActiveStatus(groupdata, groupId, status);
print("Calling handleActiveStatus with: id=$groupId, status=$status");
handleActiveStatus(groupdata, groupId.toString(), status.toString());
}
Future<void> refreshData() async {
loadAllGroups();
}
// Future<void> deleteGroupFromApi(int groupId) async {
// try {
// await apiService.deleteGroup(groupId); // your delete API call
// deleteGroup(groupId); // remove from UI list
// } catch (e) {
// print('Error deleting group: $e');
// }
// }'
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
// backgroundColor: Colors.white,
backgroundColor: Color(0xFFf5f5f5),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding: isDesktop
? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(8),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildGroupListLayout(isDesktop))
],
),
),
);
});
}
Widget buildGroupListLayout(bool isDesktop) {
return Container(
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
// decoration: BoxDecoration(
// // color: Colors.amber,
// // color: bodyColor,
// color: Color(0xFFE1F5FE),
// border: Border.all(
// color: Colors.white,
// // color: Color(0xFFF7F7FB),
// width: 3.5)),
child: buildGroupData(isDesktop),
);
}
// Widget buildGroupListView(bool isDesktop) {
// return Container(
// child: Text("DAta"),
// );
// }
Widget buildGroupData(isDesktop) {
return 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,
// ),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
'Group List',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
IconButton(
icon: const Icon(Icons.keyboard_arrow_down),
onPressed: () {},
),
],
),
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: , width: 1),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () async {
// List<dynamic> users = await futureUsers;
// context.go('/CreateGroup');
showDialog(
context: context,
builder: (context) => GroupData(
isDesktop: isDesktop,
groupId: null,
layoutColor: layoutColor!,
fetchGetGroup: refreshData
),
);
},
child: Row(
children: [
Text('New Group', style: GoogleFonts.poppins(fontSize: 12)),
SizedBox(
width: 5,
),
Icon(
Icons.add_circle_outline_rounded,
color: Colors.white,
),
],
),
),
],
),
SizedBox(
height: 5,
),
Row(
children: [
Expanded(
child: Container(
height: MediaQuery.of(context).size.height * 0.75,
padding: const EdgeInsets.all(10),
// margin: const EdgeInsets.only(bottom: 10),
color: Colors.white,
// color: Colors.red.shade100,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
buildGroupListView(isDesktop),
],
),
),
),
),
],
)
],
),
);
}
Widget buildGroupListView(bool isDesktop) {
if (apiAllGroups == null || apiAllGroups!.isEmpty) {
return Center(child: Text("No groups found."));
}
return ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: apiAllGroups!.length,
itemBuilder: (context, index) {
final group = apiAllGroups![index];
return Card(
// color: bodyColor,
// color: Color(0xFFF5F5F5),
color: Colors.white,
margin: EdgeInsets.symmetric(vertical: 6, horizontal: 10),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text("Group Name",
style: GoogleFonts.poppins(
fontSize: 11.5, fontWeight: FontWeight.w400)),
),
Expanded(
child: Text("Domestic Policy",
style: GoogleFonts.poppins(
fontSize: 11.5, fontWeight: FontWeight.w400)),
),
Expanded(
child: Text("International Policy",
style: GoogleFonts.poppins(
fontSize: 11.5, fontWeight: FontWeight.w400)),
),
Expanded(
child: Text("Description",
style: GoogleFonts.poppins(
fontSize: 11.5, fontWeight: FontWeight.w400)),
),
],
),
SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text("${group['name']}",
style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600)),
),
Expanded(
child: Text("${group['domestic_policy_name'] ?? 'N/A'}",
style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600)),
),
Expanded(
child: Text("${group['international_policy_name']}",
style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600))),
Expanded(
child: Text("${group['description'] ?? 'N/A'}",
style: GoogleFonts.poppins(
fontSize: 13, fontWeight: FontWeight.w600))),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () async {
if (group['group_id'] != null) {
final newGroupID = int.tryParse(
group['group_id'].toString());
if (newGroupID != null) {
final data = await apiService.getGroupDetailsFind(
newGroupID); // ✅ Always an int
showDialog(
context: context,
builder: (context) =>
GroupData(
isDesktop: isDesktop,
groupId: newGroupID,
// Pass the ID
groupData: data,
layoutColor: layoutColor!,
fetchGetGroup: refreshData
),
);
}
} else {
print("something went wrong check properly");
}
},
// onTap: () {
// context.go("/CreateGroup", extra: group);
// },
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(
width: 5,
),
GestureDetector(
onTap: () {
final idStr = group['group_id'];
final id = int.tryParse(idStr.toString());
if (id == null) {
print("group_id is null");
return;
}
final status = group['is_active'];
// print("GroupId : ${group['group_id']} ");
deleteGroup(group, id, status);
},
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
],
),
],
),
),
);
},
);
}
}

View File

@ -20,13 +20,14 @@ class HotelsData extends StatefulWidget {
final int? hotelsId; // <-- Add this final int? hotelsId; // <-- Add this
final Map<String, dynamic>? hotelsData; final Map<String, dynamic>? hotelsData;
const HotelsData( const HotelsData({
{super.key, super.key,
required this.isDesktop, required this.isDesktop,
this.layoutColor, this.layoutColor,
required this.fetchGetHotels, required this.fetchGetHotels,
this.hotelsId, this.hotelsId,
this.hotelsData}); this.hotelsData,
});
@override @override
HotelsDataState createState() => HotelsDataState(); HotelsDataState createState() => HotelsDataState();
@ -110,7 +111,8 @@ class HotelsDataState extends State<HotelsData> {
if (data == null) return; if (data == null) return;
setState(() { setState(() {
selectedCountry = data['country_code']; // For dropdown selectedCountry = data['country_code']; // For dropdown
selectedCountryName = data['country_name']; // For dropdown label or display selectedCountryName =
data['country_name']; // For dropdown label or display
controllers['city']?.text = data['city'] ?? ''; controllers['city']?.text = data['city'] ?? '';
controllers['hotel_chain']?.text = data['hotel_chain'] ?? ''; controllers['hotel_chain']?.text = data['hotel_chain'] ?? '';
controllers['hotel_name']?.text = data['hotel_name'] ?? ''; controllers['hotel_name']?.text = data['hotel_name'] ?? '';
@ -148,7 +150,12 @@ class HotelsDataState extends State<HotelsData> {
"city": controllers["city"]?.text, "city": controllers["city"]?.text,
}; };
final requiredFields = ["hotel_name","hotel_chain","country_code","city"]; final requiredFields = [
"hotel_name",
"hotel_chain",
"country_code",
"city",
];
// Check validation for each field // Check validation for each field
for (String field in requiredFields) { for (String field in requiredFields) {
@ -174,7 +181,6 @@ class HotelsDataState extends State<HotelsData> {
} }
Future<void> postHotelsData({int isActive = 1}) async { Future<void> postHotelsData({int isActive = 1}) async {
final hotelsData = hotels_Details(); final hotelsData = hotels_Details();
final String apiUrldata; final String apiUrldata;
@ -184,16 +190,20 @@ class HotelsDataState extends State<HotelsData> {
apiUrldata = '$apiUrl/api/updateHotels/$hotelsDataId'; apiUrldata = '$apiUrl/api/updateHotels/$hotelsDataId';
hotelsData["hotel_id"] = hotelsDataId.toString(); hotelsData["hotel_id"] = hotelsDataId.toString();
hotelsData["updated_by"] = userId; hotelsData["updated_by"] = userId;
(hotelsData.containsKey("created_by")) ? hotelsData.remove("created_by") : '' ; (hotelsData.containsKey("created_by"))
(hotelsData.containsKey("country_name")) ? hotelsData.remove("country_name") : '' ; ? hotelsData.remove("created_by")
: '';
(hotelsData.containsKey("country_name"))
? hotelsData.remove("country_name")
: '';
} else { } else {
print("for add Hotel id - null"); print("for add Hotel id - null");
apiUrldata = '$apiUrl/api/createHotels'; apiUrldata = '$apiUrl/api/createHotels';
print("called apiUrl - $apiUrldata"); print("called apiUrl - $apiUrldata");
hotelsData["created_by"] = userId; hotelsData["created_by"] = userId;
(hotelsData.containsKey("country_name")) ? hotelsData.remove("country_name") : '' ; (hotelsData.containsKey("country_name"))
? hotelsData.remove("country_name")
: '';
} }
final token = await getToken(); // Fetch token final token = await getToken(); // Fetch token
@ -210,7 +220,8 @@ class HotelsDataState extends State<HotelsData> {
}; };
final body = jsonEncode(hotelsData); final body = jsonEncode(hotelsData);
final response = hotelsDataId != null final response =
hotelsDataId != null
? await http.put(uri, headers: headers, body: body) ? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body); : await http.post(uri, headers: headers, body: body);
@ -254,7 +265,7 @@ class HotelsDataState extends State<HotelsData> {
// Map country codes to country names // Map country codes to country names
countryMap = { countryMap = {
for (var item in countryList) for (var item in countryList)
item['country_code'] as String: item['country_name'] as String item['country_code'] as String: item['country_name'] as String,
}; };
// Extract only country codes for processing // Extract only country codes for processing
@ -281,20 +292,18 @@ class HotelsDataState extends State<HotelsData> {
], ],
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Divider( Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
const SizedBox(height: 10), const SizedBox(height: 10),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Hotel Name", "Hotel Name *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -313,7 +322,8 @@ class HotelsDataState extends State<HotelsData> {
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), ),
),
), ),
if (errorMessages["hotel_name"] != null) ...[ if (errorMessages["hotel_name"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -329,11 +339,12 @@ class HotelsDataState extends State<HotelsData> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Hotel Chain", "Hotel Chain *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -352,7 +363,8 @@ class HotelsDataState extends State<HotelsData> {
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), ),
),
), ),
if (errorMessages["hotel_chain"] != null) ...[ if (errorMessages["hotel_chain"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -363,16 +375,104 @@ class HotelsDataState extends State<HotelsData> {
], ],
], ],
), ),
SizedBox(height: 10),
// - It has been observed that many of the dropdowns have overlapping issues, causing label names to be hidden - just copied searchable dropdown - still not completed (user mangement screen only ) master page except policy - my trips - flight taxi train insurance, visa misscenllo color white size padding data
// - Delete option is not working in the policy list page - completed
// - Label Names for all the modules should be set bold as it is looking like normal text in user management compared to trips page - completed
// - In the masters org mangement search option not working for traveller - particular 4 master page - - issues occur - commpleted - email master working fine, traveller master working fine, amount master working fine, group - completed
// - QC- Authentication - - completed - ask to check
const SizedBox(height: 10),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"City", "Country *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: DropdownSearch<String>(
selectedItem: countryMap[selectedCountry],
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
menuProps: const MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 250),
itemBuilder:
(context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 6.0,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Country...",
hintStyle: GoogleFonts.poppins(fontSize: 11),
contentPadding: EdgeInsets.symmetric(horizontal: 4),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: GoogleFonts.poppins(fontSize: 11),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedCountry =
countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedCountryName = newValue;
});
},
),
),
),
if (errorMessages["country_code"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["country_code"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"City *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldForexWrapper( CustomTextFieldForexWrapper(
@ -391,7 +491,8 @@ class HotelsDataState extends State<HotelsData> {
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
), ),
)), ),
),
), ),
if (errorMessages["city"] != null) ...[ if (errorMessages["city"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
@ -402,86 +503,7 @@ class HotelsDataState extends State<HotelsData> {
], ],
], ],
), ),
const SizedBox(height: 10), SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Country",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: DropdownSearch<String>(
selectedItem: countryMap[selectedCountry],
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Country...",
hintStyle: GoogleFonts.poppins(fontSize: 11),
contentPadding: EdgeInsets.symmetric(horizontal: 4),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
),
),
),
dropdownBuilder: (context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: GoogleFonts.poppins(fontSize: 11),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedCountry = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedCountryName = newValue;
});
},
),
),
),
if (errorMessages["country_code"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["country_code"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
SizedBox( height: 15 ),
// if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,), // if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
if (hotelsDataId != null) if (hotelsDataId != null)
Row( Row(
@ -492,7 +514,8 @@ class HotelsDataState extends State<HotelsData> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
Tooltip( Tooltip(
message: message:
@ -508,13 +531,10 @@ class HotelsDataState extends State<HotelsData> {
), ),
), ),
), ),
) ),
], ],
), ),
if (hotelsDataId != null) if (hotelsDataId != null) SizedBox(height: 15),
SizedBox(
height: 15,
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
@ -551,13 +571,17 @@ class HotelsDataState extends State<HotelsData> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
), ),
child: Text('Save', child: Text(
'Save',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)), fontSize: 11,
color: Colors.white,
),
),
), ),
), ),
], ],
) ),
// : SizedBox.shrink(), // : SizedBox.shrink(),
], ],
), ),

View File

@ -83,11 +83,13 @@ class HotelsDataListState extends State<HotelsDataList> {
String? bodyStringColor = await getBodyColor(); String? bodyStringColor = await getBodyColor();
setState(() { setState(() {
layoutColor = layoutString != null layoutColor =
layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyStringColor != null bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
@ -100,7 +102,7 @@ class HotelsDataListState extends State<HotelsDataList> {
Future<List<dynamic>> fetchGetHotels() async { Future<List<dynamic>> fetchGetHotels() async {
orgId = await getOrgId(); orgId = await getOrgId();
final String apiUrlData = '$apiUrl/api/getHotels'; final String apiUrlData = '$apiUrl/api/getHotels?for=table_view';
final String? token = await getToken(); final String? token = await getToken();
@ -150,7 +152,8 @@ class HotelsDataListState extends State<HotelsDataList> {
if (!data.containsKey('data') || data['data'] is! List) { if (!data.containsKey('data') || data['data'] is! List) {
throw Exception( throw Exception(
"Invalid response format: 'data' field is missing or not a List"); "Invalid response format: 'data' field is missing or not a List",
);
} }
List<dynamic> plansJson = data['data']; // 'data' is a Map, not a List List<dynamic> plansJson = data['data']; // 'data' is a Map, not a List
@ -174,8 +177,7 @@ class HotelsDataListState extends State<HotelsDataList> {
} }
} }
// Refresh user list after update
// Refresh user list after update
void refreshUserList() { void refreshUserList() {
setState(() { setState(() {
futureHotels = fetchGetHotels(); // Re-fetch users after status update futureHotels = fetchGetHotels(); // Re-fetch users after status update
@ -187,27 +189,34 @@ class HotelsDataListState extends State<HotelsDataList> {
print("allHotels before filtering: $query"); print("allHotels before filtering: $query");
final lowerQuery = query.toLowerCase(); final lowerQuery = query.toLowerCase();
setState(() { setState(() {
filteredHotels = allHotels.where((hotels) { filteredHotels =
allHotels.where((hotels) {
final isActiveStatus = final isActiveStatus =
hotels['is_active'] == "1" ? "active" : "inactive"; hotels['is_active'] == "1" ? "active" : "inactive";
return (hotels['country_code']?.toLowerCase().contains(lowerQuery) ?? return (hotels['country_code']?.toLowerCase().contains(
lowerQuery,
) ??
false) || false) ||
(hotels['country_name']?.toLowerCase().contains(lowerQuery) ?? (hotels['country_name']?.toLowerCase().contains(lowerQuery) ??
false) || false) ||
(hotels['city']?.toLowerCase().contains(lowerQuery) ?? false) || (hotels['city']?.toLowerCase().contains(lowerQuery) ?? false) ||
(hotels['hotel_chain']?.toLowerCase().contains(lowerQuery) ?? false) || (hotels['hotel_chain']?.toLowerCase().contains(lowerQuery) ??
false) ||
(hotels['hotel_name']?.toLowerCase().contains(lowerQuery) ?? (hotels['hotel_name']?.toLowerCase().contains(lowerQuery) ??
false) || false) ||
(isActiveStatus.contains(lowerQuery)); (isActiveStatus.contains(lowerQuery));
}).toList(); }).toList();
currentPage = 0;
}); });
print("filteredHotels: $filteredHotels"); print("filteredHotels: $filteredHotels");
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFf5f5f5), backgroundColor: Color(0xFFf5f5f5),
@ -216,11 +225,14 @@ class HotelsDataListState extends State<HotelsDataList> {
appBar: CustomAppBar(isDesktop: isDesktop), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding:
isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height * vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
@ -233,7 +245,8 @@ class HotelsDataListState extends State<HotelsDataList> {
), ),
), ),
); );
}); },
);
} }
Widget buildGroupList(bool isDesktop) { Widget buildGroupList(bool isDesktop) {
@ -261,7 +274,8 @@ class HotelsDataListState extends State<HotelsDataList> {
// ? EdgeInsets.all(10.0) // ? EdgeInsets.all(10.0)
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10), // padding: const EdgeInsets.all(10),
height: isDesktop height:
isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
@ -293,9 +307,7 @@ class HotelsDataListState extends State<HotelsDataList> {
], ],
), ),
if (isDesktop) if (isDesktop)
SizedBox( SizedBox(width: MediaQuery.of(context).size.width * 0.16),
width: MediaQuery.of(context).size.width * 0.16,
),
if (isDesktop) if (isDesktop)
Container( Container(
@ -307,7 +319,9 @@ class HotelsDataListState extends State<HotelsDataList> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -319,17 +333,19 @@ class HotelsDataListState extends State<HotelsDataList> {
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, width: 0.5), color: Colors.grey.shade200,
width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
// SizedBox(width: 16), // SizedBox(width: 16),
@ -343,16 +359,18 @@ class HotelsDataListState extends State<HotelsDataList> {
disabledForegroundColor: Colors.white, disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
side: side: BorderSide(color: Color(0xFF114D8B), width: 2),
BorderSide(color: Color(0xFF114D8B), width: 2),
), ),
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 20, vertical: 12), horizontal: 20,
vertical: 12,
),
), ),
onPressed: () async { onPressed: () async {
showDialog( showDialog(
context: context, context: context,
builder: (context) => HotelsData( builder:
(context) => HotelsData(
isDesktop: isDesktop, isDesktop: isDesktop,
layoutColor: layoutColor!, layoutColor: layoutColor!,
fetchGetHotels: refreshData, fetchGetHotels: refreshData,
@ -383,10 +401,7 @@ class HotelsDataListState extends State<HotelsDataList> {
], ],
), ),
if (!isDesktop) if (!isDesktop) SizedBox(height: 5),
SizedBox(
height: 5,
),
isDesktop isDesktop
? SizedBox.shrink() ? SizedBox.shrink()
: Row( : Row(
@ -401,7 +416,9 @@ class HotelsDataListState extends State<HotelsDataList> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
@ -414,17 +431,18 @@ class HotelsDataListState extends State<HotelsDataList> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade200, color: Colors.grey.shade200,
width: 0.5), width: 0.5,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey.shade300, width: 1), color: Colors.grey.shade300,
width: 1,
), ),
), ),
style: GoogleFonts.poppins(
fontSize: 12,
), ),
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
// SizedBox(width: 16), // SizedBox(width: 16),
@ -462,14 +480,17 @@ class HotelsDataListState extends State<HotelsDataList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.grey), color: Colors.grey,
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
"Please Create Hotels", "Please Create Hotels",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 16, color: Colors.grey), fontSize: 16,
color: Colors.grey,
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
], ],
@ -485,19 +506,18 @@ class HotelsDataListState extends State<HotelsDataList> {
DateTime dateA = DateTime.parse(a['created_on']); DateTime dateA = DateTime.parse(a['created_on']);
DateTime dateB = DateTime.parse(b['created_on']); DateTime dateB = DateTime.parse(b['created_on']);
return dateB return dateB.compareTo(dateA); // Descending: newest first
.compareTo(dateA); // Descending: newest first
}); });
List paginatedHotels = hotels List paginatedHotels =
hotels
.skip(currentPage * itemsPerPage) .skip(currentPage * itemsPerPage)
.take(itemsPerPage) .take(itemsPerPage)
.toList(); .toList();
Widget table = LayoutBuilder( Widget table = LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
double minWidth = double minWidth = isDesktop ? constraints.maxWidth : 1300;
isDesktop ? constraints.maxWidth : 1300;
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints(minWidth: minWidth), constraints: BoxConstraints(minWidth: minWidth),
@ -506,7 +526,9 @@ class HotelsDataListState extends State<HotelsDataList> {
columnSpacing: isDesktop ? 24.0 : 16.0, columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder( border: TableBorder(
horizontalInside: BorderSide( horizontalInside: BorderSide(
width: 0.5, color: Colors.grey.shade200), width: 0.5,
color: Colors.grey.shade200,
),
), ),
columns: [ columns: [
DataColumn( DataColumn(
@ -514,74 +536,105 @@ class HotelsDataListState extends State<HotelsDataList> {
'Hotel Name', 'Hotel Name',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Hotel Chain', 'Hotel Chain',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'City', 'City',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Country', 'Country',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Status', 'Status',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
DataColumn( DataColumn(
label: Text( label: Text(
'Actions', 'Actions',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
)), ),
),
),
], ],
rows: paginatedHotels.map((hotels) { rows:
String hotelsId = hotels['hotel_id'] paginatedHotels.map((hotels) {
String hotelsId =
hotels['hotel_id']
.toString(); // Get user ID .toString(); // Get user ID
bool isSelected = selectedUserId == hotelsId; bool isSelected = selectedUserId == hotelsId;
return DataRow(cells: [ return DataRow(
DataCell(Text(hotels['hotel_name'] ?? 'N/A', cells: [
DataCell(
Text(
hotels['hotel_name'] ?? 'N/A',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis,
DataCell(Text(hotels['hotel_chain'] ?? '', ),
),
DataCell(
Text(
hotels['hotel_chain'] ?? '',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
DataCell(Text(hotels['city'] ?? 'N/A', ),
),
DataCell(
Text(
hotels['city'] ?? 'N/A',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis,
DataCell(Text(hotels['country_name'] ?? '', ),
),
DataCell(
Text(
hotels['country_name'] ?? '',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ),
),
),
DataCell( DataCell(
Text( Text(
hotels['is_active'] == "1" hotels['is_active'] == "1"
@ -590,7 +643,10 @@ class HotelsDataListState extends State<HotelsDataList> {
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
color: hotels['is_active'] == "1" ? Colors.green : Colors.red, color:
hotels['is_active'] == "1"
? Colors.green
: Colors.grey,
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -603,17 +659,21 @@ class HotelsDataListState extends State<HotelsDataList> {
// apiService.getSingleUser(id), // apiService.getSingleUser(id),
// ), // ),
GestureDetector( GestureDetector(
child: Tooltip(
message: 'Edit Hotel Details',
child: Image.asset( child: Image.asset(
'assets/images/IconsImg/edit.png', 'assets/images/IconsImg/edit.png',
width: 20, width: 20,
height: 15), height: 15,
),
),
onTap: () async { onTap: () async {
// final userId = getUserId(user['user_id']); // final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId); // final usersData = await getUserDetails(userId);
// //
final hotelsId = int.tryParse( final hotelsId = int.tryParse(
hotels['hotel_id'] hotels['hotel_id'].toString(),
.toString()); );
if (hotelsId != null) { if (hotelsId != null) {
print("HotelsId -- $hotelsId"); print("HotelsId -- $hotelsId");
@ -623,9 +683,11 @@ class HotelsDataListState extends State<HotelsDataList> {
showDialog( showDialog(
context: context, context: context,
builder: (context) => HotelsData( builder:
(context) => HotelsData(
isDesktop: isDesktop, isDesktop: isDesktop,
hotelsId: hotelsId, // Pass the ID hotelsId:
hotelsId, // Pass the ID
hotelsData: data, hotelsData: data,
layoutColor: layoutColor!, layoutColor: layoutColor!,
// fetchGetHotels: fetchGetHotels, // fetchGetHotels: fetchGetHotels,
@ -640,7 +702,8 @@ class HotelsDataListState extends State<HotelsDataList> {
}, },
), ),
), ),
]); ],
);
}).toList(), }).toList(),
), ),
); );
@ -655,7 +718,9 @@ class HotelsDataListState extends State<HotelsDataList> {
return Card( return Card(
color: Colors.white, color: Colors.white,
margin: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(
horizontal: 12, vertical: 6), horizontal: 12,
vertical: 6,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@ -675,21 +740,26 @@ class HotelsDataListState extends State<HotelsDataList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87, color: Colors.black87,
fontWeight: FontWeight.w700), fontWeight: FontWeight.w700,
),
), ),
GestureDetector( GestureDetector(
child: Tooltip(
message: 'Edit Hotel Details',
child: Image.asset( child: Image.asset(
'assets/images/IconsImg/edit.png', 'assets/images/IconsImg/edit.png',
width: 20, width: 20,
height: 15), height: 15,
),
),
onTap: () async { onTap: () async {
// final userId = getUserId(user['user_id']); // final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId); // final usersData = await getUserDetails(userId);
// //
final hotelsId = int.tryParse( final hotelsId = int.tryParse(
hotels['hotel_id'] hotels['hotel_id'].toString(),
.toString()); );
if (hotelsId != null) { if (hotelsId != null) {
print("HotelsId -- $hotelsId"); print("HotelsId -- $hotelsId");
@ -699,7 +769,8 @@ class HotelsDataListState extends State<HotelsDataList> {
showDialog( showDialog(
context: context, context: context,
builder: (context) => HotelsData( builder:
(context) => HotelsData(
isDesktop: isDesktop, isDesktop: isDesktop,
hotelsId: hotelsId:
hotelsId, // Pass the ID hotelsId, // Pass the ID
@ -732,7 +803,8 @@ class HotelsDataListState extends State<HotelsDataList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
color: Colors.black87, color: Colors.black87,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w500,
),
), ),
], ],
), ),
@ -750,7 +822,8 @@ class HotelsDataListState extends State<HotelsDataList> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
color: Colors.black87, color: Colors.black87,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w500,
),
), ),
], ],
), ),
@ -767,7 +840,8 @@ class HotelsDataListState extends State<HotelsDataList> {
hotels['country_name'] ?? '', hotels['country_name'] ?? '',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 10, fontSize: 10,
color: Colors.black87), color: Colors.black87,
),
), ),
], ],
), ),
@ -787,7 +861,8 @@ class HotelsDataListState extends State<HotelsDataList> {
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: isDesktop child:
isDesktop
? (searchController.text.isNotEmpty && ? (searchController.text.isNotEmpty &&
filteredHotels.isEmpty filteredHotels.isEmpty
? Center( ? Center(
@ -795,7 +870,8 @@ class HotelsDataListState extends State<HotelsDataList> {
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey), color: Colors.grey,
),
), ),
) )
: SingleChildScrollView( : SingleChildScrollView(
@ -809,7 +885,8 @@ class HotelsDataListState extends State<HotelsDataList> {
"No matches found", "No matches found",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey), color: Colors.grey,
),
), ),
) )
: buildMobileCardView(paginatedHotels)), : buildMobileCardView(paginatedHotels)),
@ -843,9 +920,11 @@ class HotelsDataListState extends State<HotelsDataList> {
), ),
); );
}, },
) ),
]), ],
)), ),
),
),
); );
} }
} }

View File

@ -21,8 +21,8 @@ class FlightScreen extends StatefulWidget {
final Map<String, dynamic>? selectedItem; final Map<String, dynamic>? selectedItem;
final ValueNotifier<String?> tripTypeNotifier; final ValueNotifier<String?> tripTypeNotifier;
FlightScreen( FlightScreen({
{Key? key, Key? key,
required this.apiData, required this.apiData,
required this.loginUser, required this.loginUser,
required this.onClose, required this.onClose,
@ -32,8 +32,8 @@ class FlightScreen extends StatefulWidget {
required this.hasAction, required this.hasAction,
this.tripType, this.tripType,
required this.tripTypeNotifier, required this.tripTypeNotifier,
this.apiDataForClass}) this.apiDataForClass,
: super(key: key); }) : super(key: key);
@override @override
FlightScreenState createState() => FlightScreenState(); FlightScreenState createState() => FlightScreenState();
@ -70,7 +70,7 @@ class FlightScreenState extends State<FlightScreen> {
"_date", "_date",
"_visa", "_visa",
"_time", "_time",
"_comments" "_comments",
]; ];
Map<String, FocusNode> focusNodes = {}; Map<String, FocusNode> focusNodes = {};
@ -148,14 +148,18 @@ class FlightScreenState extends State<FlightScreen> {
// Loop through each row and add listeners to clear errors // Loop through each row and add listeners to clear errors
for (int i = 1; i <= rowCount; i++) { for (int i = 1; i <= rowCount; i++) {
textControllers["_from${i}Controller"] textControllers["_from${i}Controller"]?.addListener(
?.addListener(() => _clearError("from_place_$i")); () => _clearError("from_place_$i"),
textControllers["_to${i}Controller"] );
?.addListener(() => _clearError("to_place_$i")); textControllers["_to${i}Controller"]?.addListener(
textControllers["_date${i}Controller"] () => _clearError("to_place_$i"),
?.addListener(() => _clearError("date_$i")); );
textControllers["_time${i}Controller"] textControllers["_date${i}Controller"]?.addListener(
?.addListener(() => _clearError("time_$i")); () => _clearError("date_$i"),
);
textControllers["_time${i}Controller"]?.addListener(
() => _clearError("time_$i"),
);
} }
// loadCountryList(); // loadCountryList();
@ -178,17 +182,16 @@ class FlightScreenState extends State<FlightScreen> {
} }
Map<String, String?> getFlightTripDateRange( Map<String, String?> getFlightTripDateRange(
List<Map<String, dynamic>> flightData) { List<Map<String, dynamic>> flightData,
final allTrips = flightData ) {
final allTrips =
flightData
.expand((flight) => flight['trips'] ?? []) .expand((flight) => flight['trips'] ?? [])
.whereType<Map<String, dynamic>>() .whereType<Map<String, dynamic>>()
.toList(); .toList();
if (allTrips.isEmpty) { if (allTrips.isEmpty) {
return { return {'firstTripDate': null, 'lastTripDate': null};
'firstTripDate': null,
'lastTripDate': null,
};
} }
allTrips.sort((a, b) { allTrips.sort((a, b) {
@ -293,7 +296,8 @@ class FlightScreenState extends State<FlightScreen> {
print("Text Controllers KeysII: ${textControllers.keys.toList()}"); print("Text Controllers KeysII: ${textControllers.keys.toList()}");
// Determine the row count based on selectedTripType // Determine the row count based on selectedTripType
int rowCount = selectedTripType == "Roundtrip" int rowCount =
selectedTripType == "Roundtrip"
? 2 ? 2
: selectedTripType == "Multitrip" : selectedTripType == "Multitrip"
? multiTripRowCount ? multiTripRowCount
@ -476,10 +480,12 @@ class FlightScreenState extends State<FlightScreen> {
// TextEditingController(text: trip["from_place"]); // TextEditingController(text: trip["from_place"]);
// textControllers["_to${index}Controller"] = // textControllers["_to${index}Controller"] =
// TextEditingController(text: trip["to_place"]); // TextEditingController(text: trip["to_place"]);
textControllers["_date${index}Controller"] = textControllers["_date${index}Controller"] = TextEditingController(
TextEditingController(text: trip["date"]); text: trip["date"],
textControllers["_time${index}Controller"] = );
TextEditingController(text: trip["time"]); textControllers["_time${index}Controller"] = TextEditingController(
text: trip["time"],
);
// Check if editing and flight_trip_id exists for this trip // Check if editing and flight_trip_id exists for this trip
if (widget.selectedItem != null && if (widget.selectedItem != null &&
@ -541,10 +547,9 @@ class FlightScreenState extends State<FlightScreen> {
final currDateTime = format.parse("$currDateStr $currTimeStr"); final currDateTime = format.parse("$currDateStr $currTimeStr");
if (!currDateTime.isAfter(prevDateTime)) { if (!currDateTime.isAfter(prevDateTime)) {
errorMessages["time_$index"] = "Must be after previous time"; errorMessages["time_$index"] = "30 mins gap required";
} else if (currDateTime.difference(prevDateTime).inMinutes < 30) { } else if (currDateTime.difference(prevDateTime).inMinutes < 30) {
errorMessages["time_$index"] = errorMessages["time_$index"] = "30 mins gap required";
"Must be least 30 mins after previous time";
} else { } else {
errorMessages.remove("time_$index"); errorMessages.remove("time_$index");
} }
@ -665,14 +670,15 @@ class FlightScreenState extends State<FlightScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile; bool isMobile = sizingInfo.isMobile;
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container( return Container(
// color: Color(0xFFF4F4FB), // color: Color(0xFFF4F4FB),
// color: Color(0xFFF9F9F9), // Slightly lighter than white // color: Color(0xFFF9F9F9), // Slightly lighter than white
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Padding( child: Padding(
@ -684,13 +690,14 @@ class FlightScreenState extends State<FlightScreen> {
child: Center( child: Center(
child: Column(children: _buildAccomadtionForm(isDesktop)), child: Column(children: _buildAccomadtionForm(isDesktop)),
), ),
) ),
], ],
), ),
), ),
), ),
); );
}); },
);
} }
List<Widget> _buildAccomadtionForm(bool isDesktop) { List<Widget> _buildAccomadtionForm(bool isDesktop) {
@ -703,14 +710,14 @@ class FlightScreenState extends State<FlightScreen> {
List<List<Widget>> rowBuilders = [ List<List<Widget>> rowBuilders = [
// _builClassType(isDesktop, 1), // _builClassType(isDesktop, 1),
_buildSecondRow(isDesktop, 1) _buildSecondRow(isDesktop, 1),
]; ];
List<List<Widget>> rowRoundBuilders = [ List<List<Widget>> rowRoundBuilders = [
// _builClassType(isDesktop, 1), // _builClassType(isDesktop, 1),
_buildSecondRow(isDesktop, 1), _buildSecondRow(isDesktop, 1),
// _builClassType(isDesktop, 2), // _builClassType(isDesktop, 2),
_buildSecondRow(isDesktop, 2) _buildSecondRow(isDesktop, 2),
]; ];
print("Trip Type Selected: $selectedTripType"); print("Trip Type Selected: $selectedTripType");
@ -740,7 +747,6 @@ class FlightScreenState extends State<FlightScreen> {
// ...List.generate(multiTripRowCount, (index) => // ...List.generate(multiTripRowCount, (index) =>
// buildResponsiveRow(_builClassType(isDesktop, index + 1) + _buildSecondRow(isDesktop, index + 1)) // buildResponsiveRow(_builClassType(isDesktop, index + 1) + _buildSecondRow(isDesktop, index + 1))
// ).expand((row) => row), // ).expand((row) => row),
if (selectedTripType == "Multitrip") if (selectedTripType == "Multitrip")
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
@ -811,39 +817,40 @@ class FlightScreenState extends State<FlightScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
isDesktop isDesktop
? Row(children: _buildTripType(isDesktop)) ? Row(children: _buildTripType(isDesktop))
: Column(children: _buildTripType(isDesktop)) : Column(children: _buildTripType(isDesktop)),
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
]; ];
} }
List<Widget> _buildTripType(bool isDesktop) { List<Widget> _buildTripType(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? []; List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList List<DropdownMenuItem<String>> dropdownItems =
.map((item) => DropdownMenuItem<String>( purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_value'], value: item['dropdown_value'],
child: Text(item['dropdown_value']), child: Text(item['dropdown_value']),
)) ),
)
.toList(); .toList();
if (dropdownItems.isEmpty) { if (dropdownItems.isEmpty) {
dropdownItems.add( dropdownItems.add(
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: null, value: null,
child: Text("No options available", child: Text(
style: TextStyle(color: Colors.grey)), "No options available",
style: TextStyle(color: Colors.grey),
),
), ),
); );
} }
@ -858,7 +865,8 @@ class FlightScreenState extends State<FlightScreen> {
height: 40, height: 40,
width: double.infinity, width: double.infinity,
child: DropdownSearch<String>( child: DropdownSearch<String>(
items: purposeList items:
purposeList
.map((item) => item['dropdown_value'] as String) .map((item) => item['dropdown_value'] as String)
.toList(), .toList(),
dropdownDecoratorProps: DropDownDecoratorProps( dropdownDecoratorProps: DropDownDecoratorProps(
@ -876,11 +884,13 @@ class FlightScreenState extends State<FlightScreen> {
errorMessages.clear(); errorMessages.clear();
}); });
print( print(
"Updating form data: Flight -> trip_type -> $selectedTripType"); "Updating form data: Flight -> trip_type -> $selectedTripType",
);
_initializeFields(); _initializeFields();
}, },
selectedItem: selectedTripType, selectedItem: selectedTripType,
dropdownBuilder: (context, selectedItem) => Align( dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
selectedItem ?? "Select", selectedItem ?? "Select",
@ -890,13 +900,17 @@ class FlightScreenState extends State<FlightScreen> {
popupProps: PopupProps.menu( popupProps: PopupProps.menu(
constraints: BoxConstraints(maxHeight: 100), constraints: BoxConstraints(maxHeight: 100),
menuProps: MenuProps(backgroundColor: Colors.white), menuProps: MenuProps(backgroundColor: Colors.white),
itemBuilder: (context, item, isSelected) => Padding( itemBuilder:
padding: (context, item, isSelected) => Padding(
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 6.0,
),
child: Text( child: Text(
item, item,
style: TextStyle( style: TextStyle(
fontSize: 13), // Custom text size for dropdown items fontSize: 13,
), // Custom text size for dropdown items
), ),
), ),
), ),
@ -999,9 +1013,9 @@ class FlightScreenState extends State<FlightScreen> {
return [ return [
Container( Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
// padding: const EdgeInsets.only(left: 10, right: 10), // padding: const EdgeInsets.only(left: 10, right: 10),
// color: Colors.white, // color: Colors.white,
child: Text( child: Text(
"Trip ${index}", "Trip ${index}",
style: TextStyle( style: TextStyle(
@ -1012,17 +1026,14 @@ class FlightScreenState extends State<FlightScreen> {
), ),
), ),
SizedBox( SizedBox(
width: isDesktop width:
isDesktop
? MediaQuery.of(context).size.width * 0.58 ? MediaQuery.of(context).size.width * 0.58
: 80, // Ensure full width : 80, // Ensure full width
child: Stack( child: Stack(
alignment: Alignment.center, // Centers the icon alignment: Alignment.center, // Centers the icon
children: [ children: [
Divider( Divider(color: Color(0xFF8B8FB2), thickness: 0.5, height: 20),
color: Color(0xFF8B8FB2),
thickness: 0.5,
height: 20,
),
Container( Container(
// padding: EdgeInsets.all(4), // padding: EdgeInsets.all(4),
color: Colors.white, // Background to avoid overlapping color: Colors.white, // Background to avoid overlapping
@ -1064,7 +1075,6 @@ class FlightScreenState extends State<FlightScreen> {
// ], // ],
// ), // ),
// ), // ),
Container( Container(
// color: Colors.white, // color: Colors.white,
// padding: const EdgeInsets.only(left: 10, right: 10), // padding: const EdgeInsets.only(left: 10, right: 10),
@ -1076,7 +1086,7 @@ class FlightScreenState extends State<FlightScreen> {
color: Colors.blueAccent, color: Colors.blueAccent,
iconSize: 20, iconSize: 20,
), ),
) ),
]; ];
} }
@ -1085,19 +1095,24 @@ class FlightScreenState extends State<FlightScreen> {
List<dynamic> purposeList = widget.apiDataForClass?['flight_class'] ?? []; List<dynamic> purposeList = widget.apiDataForClass?['flight_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList List<DropdownMenuItem<String>> dropdownItems =
.map((item) => DropdownMenuItem<String>( purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_key'], value: item['dropdown_key'],
child: Text(item['dropdown_value']), child: Text(item['dropdown_value']),
)) ),
)
.toList(); .toList();
if (dropdownItems.isEmpty) { if (dropdownItems.isEmpty) {
dropdownItems.add( dropdownItems.add(
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: null, value: null,
child: Text("No options available", child: Text(
style: TextStyle(color: Colors.grey)), "No options available",
style: TextStyle(color: Colors.grey),
),
), ),
); );
} }
@ -1120,8 +1135,9 @@ class FlightScreenState extends State<FlightScreen> {
flightLastTripDateNotifier.value != null && flightLastTripDateNotifier.value != null &&
flightLastTripDateNotifier.value!.isNotEmpty) { flightLastTripDateNotifier.value!.isNotEmpty) {
try { try {
final tripDate = DateFormat('dd-MM-yyyy') final tripDate = DateFormat(
.parseStrict(flightLastTripDateNotifier.value!); 'dd-MM-yyyy',
).parseStrict(flightLastTripDateNotifier.value!);
if (tripDate.isAfter(today)) { if (tripDate.isAfter(today)) {
firstDate = tripDate; firstDate = tripDate;
} }
@ -1133,8 +1149,9 @@ class FlightScreenState extends State<FlightScreen> {
textControllers["_date${index - 1}Controller"]?.text; textControllers["_date${index - 1}Controller"]?.text;
if (previousDateString != null && previousDateString.isNotEmpty) { if (previousDateString != null && previousDateString.isNotEmpty) {
try { try {
final previousDate = final previousDate = DateFormat(
DateFormat('dd-MM-yyyy').parseStrict(previousDateString); 'dd-MM-yyyy',
).parseStrict(previousDateString);
if (previousDate.isAfter(today)) { if (previousDate.isAfter(today)) {
firstDate = previousDate; firstDate = previousDate;
} }
@ -1144,7 +1161,8 @@ class FlightScreenState extends State<FlightScreen> {
} }
} }
DateTime initialDate = _selectedCheckOutDate != null && DateTime initialDate =
_selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(firstDate) _selectedCheckOutDate!.isAfter(firstDate)
? _selectedCheckOutDate! ? _selectedCheckOutDate!
: firstDate; : firstDate;
@ -1160,14 +1178,18 @@ class FlightScreenState extends State<FlightScreen> {
setState(() { setState(() {
_selectedCheckOutDate = pickedDate; _selectedCheckOutDate = pickedDate;
// _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate); // _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
textControllers["_date${index}Controller"]?.text = textControllers["_date${index}Controller"]?.text = DateFormat(
DateFormat('dd-MM-yyyy').format(pickedDate); 'dd-MM-yyyy',
).format(pickedDate);
}); });
} }
} }
Future<void> _selectCheckOutTime( Future<void> _selectCheckOutTime(
BuildContext context, int index, VoidCallback onPicked) async { BuildContext context,
int index,
VoidCallback onPicked,
) async {
TimeOfDay? pickedTime = await showTimePicker( TimeOfDay? pickedTime = await showTimePicker(
context: context, context: context,
initialTime: _selectedCheckOutTime ?? TimeOfDay.now(), initialTime: _selectedCheckOutTime ?? TimeOfDay.now(),
@ -1179,8 +1201,13 @@ class FlightScreenState extends State<FlightScreen> {
// Formatting time to HH:mm (24-hour format) // Formatting time to HH:mm (24-hour format)
final now = DateTime.now(); final now = DateTime.now();
final formattedTime = DateFormat('HH:mm').format( final formattedTime = DateFormat('HH:mm').format(
DateTime(now.year, now.month, now.day, pickedTime.hour, DateTime(
pickedTime.minute), now.year,
now.month,
now.day,
pickedTime.hour,
pickedTime.minute,
),
); );
// _timeController.text = formattedTime; // _timeController.text = formattedTime;
textControllers["_time${index}Controller"]?.text = formattedTime; textControllers["_time${index}Controller"]?.text = formattedTime;
@ -1208,11 +1235,12 @@ class FlightScreenState extends State<FlightScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"From", "From*",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -1222,10 +1250,12 @@ class FlightScreenState extends State<FlightScreen> {
isDesktop: isDesktop, isDesktop: isDesktop,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: isCountryLoading child:
isCountryLoading
? Center(child: CircularProgressIndicator()) ? Center(child: CircularProgressIndicator())
: DropdownSearch<String>( : DropdownSearch<String>(
selectedItem: selectedFrom[index] != null selectedItem:
selectedFrom[index] != null
? countryMap[selectedFrom[index]] ? countryMap[selectedFrom[index]]
: null, : null,
popupProps: PopupProps.menu( popupProps: PopupProps.menu(
@ -1236,20 +1266,24 @@ class FlightScreenState extends State<FlightScreen> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search...", hintText: "Search...",
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(
horizontal: 10, vertical: 1), horizontal: 10,
vertical: 1,
), ),
style: TextStyle(fontSize: 12)),
menuProps: MenuProps(
backgroundColor: Colors.white,
), ),
itemBuilder: (context, item, isSelected) => Padding( style: TextStyle(fontSize: 12),
),
menuProps: MenuProps(backgroundColor: Colors.white),
itemBuilder:
(context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0), horizontal: 8.0,
vertical: 6.0,
),
child: Text( child: Text(
item, item,
style: TextStyle( style: TextStyle(
fontSize: fontSize: 13,
13), // 👈 Set your desired text size here ), // 👈 Set your desired text size here
), ),
), ),
), ),
@ -1260,7 +1294,8 @@ class FlightScreenState extends State<FlightScreen> {
contentPadding: EdgeInsets.symmetric(horizontal: 1), contentPadding: EdgeInsets.symmetric(horizontal: 1),
), ),
), ),
dropdownBuilder: (context, selectedItem) => Align( dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
selectedItem ?? "Select", selectedItem ?? "Select",
@ -1273,15 +1308,18 @@ class FlightScreenState extends State<FlightScreen> {
// .firstWhere((entry) => entry.value == newValue) // .firstWhere((entry) => entry.value == newValue)
// .key; // .key;
selectedFrom[index] = countryMap.entries selectedFrom[index] =
.firstWhere((entry) => entry.value == newValue) countryMap.entries
.firstWhere(
(entry) => entry.value == newValue,
)
.key; .key;
print(selectedFrom[index]); print(selectedFrom[index]);
}); });
}, },
), ),
) ),
// child: SizedBox( // child: SizedBox(
// height: 40, // height: 40,
@ -1302,30 +1340,21 @@ class FlightScreenState extends State<FlightScreen> {
), ),
if (errorMessages["from_place_$index"] != null) ...[ if (errorMessages["from_place_$index"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) SizedBox(width: 20) else SizedBox(height: 8),
SizedBox(
width: 20,
)
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"To", "To*",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -1333,10 +1362,12 @@ class FlightScreenState extends State<FlightScreen> {
isDesktop: isDesktop, isDesktop: isDesktop,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: isCountryLoading child:
isCountryLoading
? Center(child: CircularProgressIndicator()) ? Center(child: CircularProgressIndicator())
: DropdownSearch<String>( : DropdownSearch<String>(
selectedItem: selectedTo[index] != null selectedItem:
selectedTo[index] != null
? countryMap[selectedTo[index]] ? countryMap[selectedTo[index]]
: null, : null,
popupProps: PopupProps.menu( popupProps: PopupProps.menu(
@ -1347,20 +1378,24 @@ class FlightScreenState extends State<FlightScreen> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search...", hintText: "Search...",
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(
horizontal: 10, vertical: 1), horizontal: 10,
vertical: 1,
), ),
style: TextStyle(fontSize: 12)),
menuProps: MenuProps(
backgroundColor: Colors.white,
), ),
itemBuilder: (context, item, isSelected) => Padding( style: TextStyle(fontSize: 12),
),
menuProps: MenuProps(backgroundColor: Colors.white),
itemBuilder:
(context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0), horizontal: 8.0,
vertical: 6.0,
),
child: Text( child: Text(
item, item,
style: TextStyle( style: TextStyle(
fontSize: fontSize: 13,
13), // 👈 Set your desired text size here ), // 👈 Set your desired text size here
), ),
), ),
), ),
@ -1371,7 +1406,8 @@ class FlightScreenState extends State<FlightScreen> {
contentPadding: EdgeInsets.symmetric(horizontal: 1), contentPadding: EdgeInsets.symmetric(horizontal: 1),
), ),
), ),
dropdownBuilder: (context, selectedItem) => Align( dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
selectedItem ?? "Select Country", selectedItem ?? "Select Country",
@ -1380,30 +1416,26 @@ class FlightScreenState extends State<FlightScreen> {
), ),
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() {
selectedTo[index] = countryMap.entries selectedTo[index] =
.firstWhere((entry) => entry.value == newValue) countryMap.entries
.firstWhere(
(entry) => entry.value == newValue,
)
.key; .key;
print(selectedTo[index]); print(selectedTo[index]);
}); });
}, },
), ),
)), ),
),
if (errorMessages["to_place_$index"] != null) ...[ if (errorMessages["to_place_$index"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -1412,7 +1444,8 @@ class FlightScreenState extends State<FlightScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -1423,7 +1456,8 @@ class FlightScreenState extends State<FlightScreen> {
// : MediaQuery.of(context).size.width * 0.66, // : MediaQuery.of(context).size.width * 0.66,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: isFlightClassLoading child:
isFlightClassLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: DropdownButtonFormField<String>( : DropdownButtonFormField<String>(
focusNode: focusNodes["_class${index}FocusNode"], focusNode: focusNodes["_class${index}FocusNode"],
@ -1435,9 +1469,11 @@ class FlightScreenState extends State<FlightScreen> {
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding horizontal: 10,
), // Proper padding
), ),
onChanged: purposeList.isNotEmpty onChanged:
purposeList.isNotEmpty
? (newValue) { ? (newValue) {
setState(() { setState(() {
selectedClasses[index] = newValue; selectedClasses[index] = newValue;
@ -1452,21 +1488,17 @@ class FlightScreenState extends State<FlightScreen> {
), ),
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Date", "Date*",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -1489,8 +1521,11 @@ class FlightScreenState extends State<FlightScreen> {
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today, suffixIcon: Icon(
size: 16, color: Colors.grey), Icons.calendar_today,
size: 16,
color: Colors.grey,
),
), ),
), ),
), ),
@ -1499,28 +1534,21 @@ class FlightScreenState extends State<FlightScreen> {
), ),
if (errorMessages["date_$index"] != null) ...[ if (errorMessages["date_$index"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Time", "Time*",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -1538,7 +1566,8 @@ class FlightScreenState extends State<FlightScreen> {
_selectCheckOutTime(context, index, () { _selectCheckOutTime(context, index, () {
validateTimeDifference(index); validateTimeDifference(index);
setState( setState(
() {}); // ✅ Force rebuild to show the error immediately () {},
); // ✅ Force rebuild to show the error immediately
}); });
}, },
child: AbsorbPointer( child: AbsorbPointer(
@ -1554,8 +1583,11 @@ class FlightScreenState extends State<FlightScreen> {
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: suffixIcon: Icon(
Icon(Icons.access_time, size: 16, color: Colors.grey), Icons.access_time,
size: 16,
color: Colors.grey,
),
), ),
), ),
), ),
@ -1579,13 +1611,9 @@ class FlightScreenState extends State<FlightScreen> {
onPressed: () { onPressed: () {
removeTrip(index); removeTrip(index);
}, },
icon: Icon( icon: Icon(Icons.close, color: Colors.redAccent, size: 20),
Icons.close,
color: Colors.redAccent,
size: 20,
), ),
), ),
)
]; ];
} }
@ -1594,11 +1622,14 @@ class FlightScreenState extends State<FlightScreen> {
widget.apiData?['flight_visa_available'] ?? []; widget.apiData?['flight_visa_available'] ?? [];
// Default selected value // Default selected value
List<DropdownMenuItem<String>> dropdownItems = visa_available List<DropdownMenuItem<String>> dropdownItems =
.map((item) => DropdownMenuItem<String>( visa_available
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_key'], value: item['dropdown_key'],
child: Text(item['dropdown_value']), child: Text(item['dropdown_value']),
)) ),
)
.toList(); .toList();
selectedvisa_available ??= selectedvisa_available ??=
@ -1608,8 +1639,10 @@ class FlightScreenState extends State<FlightScreen> {
dropdownItems.add( dropdownItems.add(
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: null, value: null,
child: Text("No options available", child: Text(
style: TextStyle(color: Colors.grey)), "No options available",
style: TextStyle(color: Colors.grey),
),
), ),
); );
} }
@ -1622,7 +1655,8 @@ class FlightScreenState extends State<FlightScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -1641,10 +1675,12 @@ class FlightScreenState extends State<FlightScreen> {
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: contentPadding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 10), // Proper padding horizontal: 10,
), // Proper padding
), ),
onChanged: visa_available.isNotEmpty onChanged:
visa_available.isNotEmpty
? (newValue) { ? (newValue) {
setState(() { setState(() {
selectedvisa_available = newValue; selectedvisa_available = newValue;
@ -1652,7 +1688,8 @@ class FlightScreenState extends State<FlightScreen> {
// Reset `multiTripRowCount` when switching away from Multitrip // Reset `multiTripRowCount` when switching away from Multitrip
}); });
print( print(
"Updating form data: Flight -> trip_type -> $selectedvisa_available"); "Updating form data: Flight -> trip_type -> $selectedvisa_available",
);
// _initializeRows(); // _initializeRows();
} }
@ -1664,13 +1701,8 @@ class FlightScreenState extends State<FlightScreen> {
), ),
], ],
), ),
if (isDesktop) if (isDesktop) SizedBox(width: 20),
SizedBox( SizedBox(height: 5),
width: 20,
),
SizedBox(
height: 5,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -1679,7 +1711,8 @@ class FlightScreenState extends State<FlightScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldWrapper( CustomTextFieldWrapper(
@ -1705,9 +1738,7 @@ class FlightScreenState extends State<FlightScreen> {
], ],
), ),
if (isDesktop) Spacer(), if (isDesktop) Spacer(),
SizedBox( SizedBox(height: 5),
height: 5,
),
Column( Column(
children: [ children: [
Row( Row(
@ -1724,11 +1755,14 @@ class FlightScreenState extends State<FlightScreen> {
widget.apiData?['flight_visa_available'] ?? []; widget.apiData?['flight_visa_available'] ?? [];
// Default selected value // Default selected value
List<DropdownMenuItem<String>> dropdownItems = visa_available List<DropdownMenuItem<String>> dropdownItems =
.map((item) => DropdownMenuItem<String>( visa_available
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_key'], value: item['dropdown_key'],
child: Text(item['dropdown_value']), child: Text(item['dropdown_value']),
)) ),
)
.toList(); .toList();
selectedvisa_available ??= selectedvisa_available ??=
@ -1738,8 +1772,10 @@ class FlightScreenState extends State<FlightScreen> {
dropdownItems.add( dropdownItems.add(
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: null, value: null,
child: Text("No options available", child: Text(
style: TextStyle(color: Colors.grey)), "No options available",
style: TextStyle(color: Colors.grey),
),
), ),
); );
} }
@ -1753,14 +1789,16 @@ class FlightScreenState extends State<FlightScreen> {
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldWrapper( CustomTextFieldWrapper(
// isFocused: _tripTypeFocused, // isFocused: _tripTypeFocused,
isFocused: focusStates["_visa1Focused"] ?? false, isFocused: focusStates["_visa1Focused"] ?? false,
isDesktop: isDesktop, isDesktop: isDesktop,
width: isDesktop width:
isDesktop
? MediaQuery.of(context).size.width * 0.34 ? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66, : MediaQuery.of(context).size.width * 0.66,
child: SizedBox( child: SizedBox(
@ -1772,10 +1810,12 @@ class FlightScreenState extends State<FlightScreen> {
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: contentPadding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 10), // Proper padding horizontal: 10,
), // Proper padding
), ),
onChanged: visa_available.isNotEmpty onChanged:
visa_available.isNotEmpty
? (newValue) { ? (newValue) {
setState(() { setState(() {
selectedvisa_available = newValue; selectedvisa_available = newValue;
@ -1783,7 +1823,8 @@ class FlightScreenState extends State<FlightScreen> {
// Reset `multiTripRowCount` when switching away from Multitrip // Reset `multiTripRowCount` when switching away from Multitrip
}); });
print( print(
"Updating form data: Flight -> trip_type -> $selectedvisa_available"); "Updating form data: Flight -> trip_type -> $selectedvisa_available",
);
// _initializeRows(); // _initializeRows();
} }
@ -1807,9 +1848,7 @@ class FlightScreenState extends State<FlightScreen> {
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color backgroundColor: Colors.grey[400], // Light grey color
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
child: Text( child: Text(
@ -1818,7 +1857,6 @@ class FlightScreenState extends State<FlightScreen> {
), ),
), ),
SizedBox(width: 10), // Space between buttons SizedBox(width: 10), // Space between buttons
// Save Changes Button // Save Changes Button
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
@ -1826,9 +1864,7 @@ class FlightScreenState extends State<FlightScreen> {
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B), // Primary color for save backgroundColor: Color(0xFF114D8B), // Primary color for save
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
child: Text( child: Text(

File diff suppressed because it is too large Load Diff

View File

@ -14,12 +14,13 @@ class TaxiScreen extends StatefulWidget {
final Map<String, dynamic>? selectedItem; final Map<String, dynamic>? selectedItem;
final String? loginUser; final String? loginUser;
TaxiScreen( TaxiScreen({
{required this.onClose, required this.onClose,
this.apiData, this.apiData,
required this.onSavetaxi, required this.onSavetaxi,
required this.selectedItem, required this.selectedItem,
required this.loginUser}); required this.loginUser,
});
@override @override
_TaxiScreenState createState() => _TaxiScreenState(); _TaxiScreenState createState() => _TaxiScreenState();
@ -97,13 +98,17 @@ class _TaxiScreenState extends State<TaxiScreen> {
super.initState(); super.initState();
_addFocusListener( _addFocusListener(
_destinationFocusNode, (focus) => _destinationFocus = focus); _destinationFocusNode,
(focus) => _destinationFocus = focus,
);
_addFocusListener(_locationFocusNode, (focus) => _locationFocus = focus); _addFocusListener(_locationFocusNode, (focus) => _locationFocus = focus);
_addFocusListener(_dateFocusNode, (focus) => _dateFocus = focus); _addFocusListener(_dateFocusNode, (focus) => _dateFocus = focus);
_addFocusListener(_timeFocusNode, (focus) => _timeFocus = focus); _addFocusListener(_timeFocusNode, (focus) => _timeFocus = focus);
_addFocusListener(_taxiReqFocusNode, (focus) => _taxiReqFocused = focus); _addFocusListener(_taxiReqFocusNode, (focus) => _taxiReqFocused = focus);
_addFocusListener( _addFocusListener(
_numPassengerFocusNode, (focus) => _numPassengerFocus = focus); _numPassengerFocusNode,
(focus) => _numPassengerFocus = focus,
);
_addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus); _addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus);
_destinationController = initController("destination_city"); _destinationController = initController("destination_city");
@ -168,7 +173,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
"location_of_pickup", "location_of_pickup",
"no_of_passengers", "no_of_passengers",
"date", "date",
"time" "time",
]; ];
// Check validation for each field // Check validation for each field
@ -199,9 +204,11 @@ class _TaxiScreenState extends State<TaxiScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile; bool isMobile = sizingInfo.isMobile;
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container( return Container(
// color: Color(0xFFF4F4FB), // color: Color(0xFFF4F4FB),
@ -216,13 +223,14 @@ class _TaxiScreenState extends State<TaxiScreen> {
child: Center( child: Center(
child: Column(children: _buildAccomadtionForm(isDesktop)), child: Column(children: _buildAccomadtionForm(isDesktop)),
), ),
) ),
], ],
), ),
), ),
), ),
); );
}); },
);
} }
List<Widget> _buildAccomadtionForm(bool isDesktop) { List<Widget> _buildAccomadtionForm(bool isDesktop) {
@ -235,7 +243,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
List<List<Widget>> rowBuilders = [ List<List<Widget>> rowBuilders = [
// _builClassType(isDesktop), // _builClassType(isDesktop),
_buildSecondRow(isDesktop) _buildSecondRow(isDesktop),
]; ];
return [ return [
@ -253,19 +261,24 @@ class _TaxiScreenState extends State<TaxiScreen> {
List<Widget> _buildFirstRow(isDesktop) { List<Widget> _buildFirstRow(isDesktop) {
List<dynamic> purposeList = widget.apiData?['taxt_car_type'] ?? []; List<dynamic> purposeList = widget.apiData?['taxt_car_type'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList List<DropdownMenuItem<String>> dropdownItems =
.map((item) => DropdownMenuItem<String>( purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_key'], value: item['dropdown_key'],
child: Text(item['dropdown_value']), child: Text(item['dropdown_value']),
)) ),
)
.toList(); .toList();
if (dropdownItems.isEmpty) { if (dropdownItems.isEmpty) {
dropdownItems.add( dropdownItems.add(
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: null, value: null,
child: Text("No options available", child: Text(
style: TextStyle(color: Colors.grey)), "No options available",
style: TextStyle(color: Colors.grey),
),
), ),
); );
} }
@ -283,20 +296,16 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
isDesktop isDesktop
? Row(children: _buildTripType(isDesktop)) ? Row(children: _buildTripType(isDesktop))
: Column(children: _buildTripType(isDesktop)) : Column(children: _buildTripType(isDesktop)),
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -305,7 +314,8 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -319,8 +329,9 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
keyboardType: TextInputType.numberWithOptions(decimal: true), keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.allow(RegExp( FilteringTextInputFormatter.allow(
r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal RegExp(r'^\d*\.?\d*$'),
), // Allow only positive numbers with optional decimal
], ],
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "Number of Passenger", labelText: "Number of Passenger",
@ -334,19 +345,11 @@ class _TaxiScreenState extends State<TaxiScreen> {
), ),
if (errorMessages["no_of_passengers"] != null) ...[ if (errorMessages["no_of_passengers"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -355,7 +358,8 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -369,16 +373,19 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: contentPadding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 10), // Proper padding horizontal: 10,
), // Proper padding
), ),
onChanged: purposeList.isNotEmpty onChanged:
purposeList.isNotEmpty
? (newValue) { ? (newValue) {
setState(() { setState(() {
selectedCarType = newValue; selectedCarType = newValue;
}); });
print( print(
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}"); "Updating form data: Flight -> trip_type -> ${newValue ?? ""}",
);
} }
: null, : null,
@ -388,31 +395,31 @@ class _TaxiScreenState extends State<TaxiScreen> {
), ),
], ],
), ),
if (isDesktop) if (isDesktop) SizedBox.shrink() else SizedBox(height: 8),
SizedBox.shrink()
else
SizedBox(
height: 8,
),
]; ];
} }
List<Widget> _buildTripType(bool isDesktop) { List<Widget> _buildTripType(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['taxi_car_required_for'] ?? []; List<dynamic> purposeList = widget.apiData?['taxi_car_required_for'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList List<DropdownMenuItem<String>> dropdownItems =
.map((item) => DropdownMenuItem<String>( purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_key'], value: item['dropdown_key'],
child: Text(item['dropdown_value']), child: Text(item['dropdown_value']),
)) ),
)
.toList(); .toList();
if (dropdownItems.isEmpty) { if (dropdownItems.isEmpty) {
dropdownItems.add( dropdownItems.add(
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: null, value: null,
child: Text("No options available", child: Text(
style: TextStyle(color: Colors.grey)), "No options available",
style: TextStyle(color: Colors.grey),
),
), ),
); );
} }
@ -425,7 +432,8 @@ class _TaxiScreenState extends State<TaxiScreen> {
CustomTextFieldWrapper( CustomTextFieldWrapper(
isFocused: _taxiReqFocused, isFocused: _taxiReqFocused,
isDesktop: isDesktop, isDesktop: isDesktop,
width: isDesktop width:
isDesktop
? MediaQuery.of(context).size.width * 0.34 ? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66, : MediaQuery.of(context).size.width * 0.66,
child: SizedBox( child: SizedBox(
@ -436,16 +444,19 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: contentPadding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 10), // Proper padding horizontal: 10,
), // Proper padding
), ),
onChanged: purposeList.isNotEmpty onChanged:
purposeList.isNotEmpty
? (newValue) { ? (newValue) {
setState(() { setState(() {
selectedReqTaxi = newValue; selectedReqTaxi = newValue;
}); });
print( print(
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}"); "Updating form data: Flight -> trip_type -> ${newValue ?? ""}",
);
} }
: null, : null,
@ -466,7 +477,8 @@ class _TaxiScreenState extends State<TaxiScreen> {
DateTime? pickedDate = await showDatePicker( DateTime? pickedDate = await showDatePicker(
context: context, context: context,
initialDate: _selectedCheckOutDate != null && initialDate:
_selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today) _selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate! ? _selectedCheckOutDate!
: today, : today,
@ -494,8 +506,13 @@ class _TaxiScreenState extends State<TaxiScreen> {
// Formatting time to HH:mm (24-hour format) // Formatting time to HH:mm (24-hour format)
final now = DateTime.now(); final now = DateTime.now();
final formattedTime = DateFormat('HH:mm').format( final formattedTime = DateFormat('HH:mm').format(
DateTime(now.year, now.month, now.day, pickedTime.hour, DateTime(
pickedTime.minute), now.year,
now.month,
now.day,
pickedTime.hour,
pickedTime.minute,
),
); );
_timeController.text = formattedTime; _timeController.text = formattedTime;
}); });
@ -511,7 +528,8 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -535,28 +553,21 @@ class _TaxiScreenState extends State<TaxiScreen> {
), ),
if (errorMessages["destination_city"] != null) ...[ if (errorMessages["destination_city"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Location of Pickup", "Pickup Location",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -569,7 +580,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
controller: _locationController, controller: _locationController,
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "Location of Pickup", labelText: "Pickup Location",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey), labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
@ -580,19 +591,11 @@ class _TaxiScreenState extends State<TaxiScreen> {
), ),
if (errorMessages["location_of_pickup"] != null) ...[ if (errorMessages["location_of_pickup"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -601,7 +604,8 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -622,8 +626,11 @@ class _TaxiScreenState extends State<TaxiScreen> {
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today, suffixIcon: Icon(
size: 16, color: Colors.grey), Icons.calendar_today,
size: 16,
color: Colors.grey,
),
), ),
), ),
), ),
@ -632,19 +639,11 @@ class _TaxiScreenState extends State<TaxiScreen> {
), ),
if (errorMessages["date"] != null) ...[ if (errorMessages["date"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -653,7 +652,8 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -674,8 +674,11 @@ class _TaxiScreenState extends State<TaxiScreen> {
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: suffixIcon: Icon(
Icon(Icons.access_time, size: 16, color: Colors.grey), Icons.access_time,
size: 16,
color: Colors.grey,
),
), ),
), ),
), ),
@ -684,10 +687,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
), ),
if (errorMessages["time"] != null) ...[ if (errorMessages["time"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
@ -704,13 +704,15 @@ class _TaxiScreenState extends State<TaxiScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldWrapper( CustomTextFieldWrapper(
isFocused: _commentsFocus, // Dropdown doesn't use focus isFocused: _commentsFocus, // Dropdown doesn't use focus
isDesktop: isDesktop, isDesktop: isDesktop,
width: isDesktop width:
isDesktop
? MediaQuery.of(context).size.width * 0.34 ? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66, : MediaQuery.of(context).size.width * 0.66,
@ -733,9 +735,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
], ],
), ),
if (isDesktop) Spacer(), if (isDesktop) Spacer(),
SizedBox( SizedBox(height: 5),
height: 5,
),
Column( Column(
children: [ children: [
Row( Row(
@ -756,9 +756,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color backgroundColor: Colors.grey[400], // Light grey color
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
child: Text( child: Text(
@ -767,7 +765,6 @@ class _TaxiScreenState extends State<TaxiScreen> {
), ),
), ),
SizedBox(width: 10), // Space between buttons SizedBox(width: 10), // Space between buttons
// Save Changes Button // Save Changes Button
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
@ -775,9 +772,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B), // Primary color for save backgroundColor: Color(0xFF114D8B), // Primary color for save
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
child: Text( child: Text(

View File

@ -17,14 +17,15 @@ class TrainScreen extends StatefulWidget {
final String? loginUser; final String? loginUser;
final String? tripType; final String? tripType;
TrainScreen( TrainScreen({
{required this.onClose, required this.onClose,
this.apiData, this.apiData,
required this.onSavetrain, required this.onSavetrain,
required this.selectedItem, required this.selectedItem,
required this.loginUser, required this.loginUser,
this.apiDataForClass, this.apiDataForClass,
this.tripType}); this.tripType,
});
@override @override
_TrainScreenState createState() => _TrainScreenState(); _TrainScreenState createState() => _TrainScreenState();
@ -232,7 +233,7 @@ class _TrainScreenState extends State<TrainScreen> {
"from_station", "from_station",
"to_station", "to_station",
"date", "date",
"time" "time",
]; ];
// Check validation for each field // Check validation for each field
@ -287,9 +288,11 @@ class _TrainScreenState extends State<TrainScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile; bool isMobile = sizingInfo.isMobile;
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container( return Container(
// color: Color(0xFFF4F4FB), // color: Color(0xFFF4F4FB),
@ -325,13 +328,14 @@ class _TrainScreenState extends State<TrainScreen> {
child: Center( child: Center(
child: Column(children: _buildAccomadtionForm(isDesktop)), child: Column(children: _buildAccomadtionForm(isDesktop)),
), ),
) ),
], ],
), ),
), ),
), ),
); );
}); },
);
} }
List<Widget> _buildAccomadtionForm(bool isDesktop) { List<Widget> _buildAccomadtionForm(bool isDesktop) {
@ -344,7 +348,7 @@ class _TrainScreenState extends State<TrainScreen> {
List<List<Widget>> rowBuilders = [ List<List<Widget>> rowBuilders = [
_builClassType(isDesktop), _builClassType(isDesktop),
_buildSecondRow(isDesktop) _buildSecondRow(isDesktop),
]; ];
return [ return [
@ -369,7 +373,8 @@ class _TrainScreenState extends State<TrainScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
isDesktop isDesktop
@ -377,38 +382,35 @@ class _TrainScreenState extends State<TrainScreen> {
: Column(children: _buildTripType(isDesktop)), : Column(children: _buildTripType(isDesktop)),
if (errorMessages["train_no"] != null) ...[ if (errorMessages["train_no"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
]; ];
} }
List<Widget> _buildTripType(bool isDesktop) { List<Widget> _buildTripType(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? []; List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList List<DropdownMenuItem<String>> dropdownItems =
.map((item) => DropdownMenuItem<String>( purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_value'], value: item['dropdown_value'],
child: Text(item['dropdown_value']), child: Text(item['dropdown_value']),
)) ),
)
.toList(); .toList();
if (dropdownItems.isEmpty) { if (dropdownItems.isEmpty) {
dropdownItems.add( dropdownItems.add(
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: null, value: null,
child: Text("No options available", child: Text(
style: TextStyle(color: Colors.grey)), "No options available",
style: TextStyle(color: Colors.grey),
),
), ),
); );
} }
@ -455,7 +457,8 @@ class _TrainScreenState extends State<TrainScreen> {
DateTime? pickedDate = await showDatePicker( DateTime? pickedDate = await showDatePicker(
context: context, context: context,
initialDate: _selectedCheckOutDate != null && initialDate:
_selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today) _selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate! ? _selectedCheckOutDate!
: today, : today,
@ -483,8 +486,13 @@ class _TrainScreenState extends State<TrainScreen> {
// Formatting time to HH:mm (24-hour format) // Formatting time to HH:mm (24-hour format)
final now = DateTime.now(); final now = DateTime.now();
final formattedTime = DateFormat('HH:mm').format( final formattedTime = DateFormat('HH:mm').format(
DateTime(now.year, now.month, now.day, pickedTime.hour, DateTime(
pickedTime.minute), now.year,
now.month,
now.day,
pickedTime.hour,
pickedTime.minute,
),
); );
_timeController.text = formattedTime; _timeController.text = formattedTime;
}); });
@ -495,19 +503,24 @@ class _TrainScreenState extends State<TrainScreen> {
List<dynamic> purposeList = widget.apiDataForClass?['train_class'] ?? []; List<dynamic> purposeList = widget.apiDataForClass?['train_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList List<DropdownMenuItem<String>> dropdownItems =
.map((item) => DropdownMenuItem<String>( purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_key'], value: item['dropdown_key'],
child: Text(item['dropdown_value']), child: Text(item['dropdown_value']),
)) ),
)
.toList(); .toList();
if (dropdownItems.isEmpty) { if (dropdownItems.isEmpty) {
dropdownItems.add( dropdownItems.add(
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: null, value: null,
child: Text("No options available", child: Text(
style: TextStyle(color: Colors.grey)), "No options available",
style: TextStyle(color: Colors.grey),
),
), ),
); );
} }
@ -525,7 +538,8 @@ class _TrainScreenState extends State<TrainScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -543,10 +557,12 @@ class _TrainScreenState extends State<TrainScreen> {
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: contentPadding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 10), // Proper padding horizontal: 10,
), // Proper padding
), ),
onChanged: purposeList.isNotEmpty onChanged:
purposeList.isNotEmpty
? (newValue) { ? (newValue) {
setState(() { setState(() {
selectedClass = newValue; selectedClass = newValue;
@ -559,19 +575,11 @@ class _TrainScreenState extends State<TrainScreen> {
), ),
if (errorMessages["class"] != null) ...[ if (errorMessages["class"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -580,7 +588,8 @@ class _TrainScreenState extends State<TrainScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -588,41 +597,51 @@ class _TrainScreenState extends State<TrainScreen> {
isDesktop: isDesktop, isDesktop: isDesktop,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: isCountryLoading child:
isCountryLoading
? Center(child: CircularProgressIndicator()) ? Center(child: CircularProgressIndicator())
: DropdownSearch<String>( : DropdownSearch<String>(
// selectedItem: selectedFrom != null // selectedItem: selectedFrom != null
// ? countryMap[selectedFrom] // ? countryMap[selectedFrom]
// : null, // : null,
selectedItem:
selectedItem: selectedFrom != null selectedFrom != null
? countryMap[ ? countryMap[selectedFrom] // get the display value from code
selectedFrom] // get the display value from code
: null, : null,
popupProps: PopupProps.menu( popupProps: PopupProps.menu(
menuProps: MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 230),
showSearchBox: true, showSearchBox: true,
searchFieldProps: TextFieldProps( searchFieldProps: TextFieldProps(
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
contentPadding: contentPadding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 10), horizontal: 10,
), ),
), ),
), ),
),
items: countryMap.values.toList(), items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps( dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration( dropdownSearchDecoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
), ),
), ),
dropdownBuilder: (context, selectedItem) => Align( dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
selectedItem ?? "Select", selectedItem ?? "Select",
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
), ),
), ),
// onChanged: (String? newValue) { // onChanged: (String? newValue) {
// setState(() { // setState(() {
// // selectedFrom[index] = countryMap.entries // // selectedFrom[index] = countryMap.entries
@ -636,16 +655,18 @@ class _TrainScreenState extends State<TrainScreen> {
// print(selectedFrom); // print(selectedFrom);
// }); // });
// }, // },
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() {
selectedFrom = countryMap.entries selectedFrom =
.firstWhere((entry) => entry.value == newValue) countryMap.entries
.firstWhere(
(entry) => entry.value == newValue,
)
.key; .key;
}); });
}, },
), ),
) ),
// child: SizedBox( // child: SizedBox(
// height: 40, // height: 40,
// child: TextField( // child: TextField(
@ -664,19 +685,11 @@ class _TrainScreenState extends State<TrainScreen> {
), ),
if (errorMessages["from_station"] != null) ...[ if (errorMessages["from_station"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -685,7 +698,8 @@ class _TrainScreenState extends State<TrainScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -693,20 +707,24 @@ class _TrainScreenState extends State<TrainScreen> {
isDesktop: isDesktop, isDesktop: isDesktop,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: isCountryLoading child:
isCountryLoading
? Center(child: CircularProgressIndicator()) ? Center(child: CircularProgressIndicator())
: DropdownSearch<String>( : DropdownSearch<String>(
selectedItem: selectedTo != null selectedItem:
? countryMap[ selectedTo != null
selectedTo] // get the display value from code ? countryMap[selectedTo] // get the display value from code
: null, : null,
popupProps: PopupProps.menu( popupProps: PopupProps.menu(
menuProps: MenuProps(backgroundColor: Colors.white),
constraints: BoxConstraints(maxHeight: 230),
showSearchBox: true, showSearchBox: true,
searchFieldProps: TextFieldProps( searchFieldProps: TextFieldProps(
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search ...", hintText: "Search ...",
contentPadding: contentPadding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 10), horizontal: 10,
),
), ),
), ),
), ),
@ -717,7 +735,8 @@ class _TrainScreenState extends State<TrainScreen> {
contentPadding: EdgeInsets.symmetric(horizontal: 1), contentPadding: EdgeInsets.symmetric(horizontal: 1),
), ),
), ),
dropdownBuilder: (context, selectedItem) => Align( dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
selectedItem ?? "Select", selectedItem ?? "Select",
@ -726,8 +745,11 @@ class _TrainScreenState extends State<TrainScreen> {
), ),
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() {
selectedTo = countryMap.entries selectedTo =
.firstWhere((entry) => entry.value == newValue) countryMap.entries
.firstWhere(
(entry) => entry.value == newValue,
)
.key; .key;
}); });
}, },
@ -736,19 +758,11 @@ class _TrainScreenState extends State<TrainScreen> {
), ),
if (errorMessages["to_station"] != null) ...[ if (errorMessages["to_station"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -757,7 +771,8 @@ class _TrainScreenState extends State<TrainScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -779,8 +794,11 @@ class _TrainScreenState extends State<TrainScreen> {
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today, suffixIcon: Icon(
size: 16, color: Colors.grey), Icons.calendar_today,
size: 16,
color: Colors.grey,
),
), ),
), ),
), ),
@ -789,19 +807,11 @@ class _TrainScreenState extends State<TrainScreen> {
), ),
if (errorMessages["date"] != null) ...[ if (errorMessages["date"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
if (isDesktop) if (isDesktop) Spacer() else SizedBox(height: 8),
Spacer()
else
SizedBox(
height: 8,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -810,7 +820,8 @@ class _TrainScreenState extends State<TrainScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper( CustomTextFieldItnerarySubWrapper(
@ -832,8 +843,11 @@ class _TrainScreenState extends State<TrainScreen> {
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: suffixIcon: Icon(
Icon(Icons.access_time, size: 16, color: Colors.grey), Icons.access_time,
size: 16,
color: Colors.grey,
),
), ),
), ),
), ),
@ -842,10 +856,7 @@ class _TrainScreenState extends State<TrainScreen> {
), ),
if (errorMessages["time"] != null) ...[ if (errorMessages["time"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
], ],
], ],
), ),
@ -862,7 +873,8 @@ class _TrainScreenState extends State<TrainScreen> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldWrapper( CustomTextFieldWrapper(
@ -890,9 +902,7 @@ class _TrainScreenState extends State<TrainScreen> {
], ],
), ),
if (isDesktop) Spacer(), if (isDesktop) Spacer(),
SizedBox( SizedBox(height: 5),
height: 5,
),
Column( Column(
children: [ children: [
Row( Row(
@ -900,7 +910,7 @@ class _TrainScreenState extends State<TrainScreen> {
children: _handleAction(isDesktop), children: _handleAction(isDesktop),
), ),
], ],
) ),
]; ];
} }
@ -913,9 +923,7 @@ class _TrainScreenState extends State<TrainScreen> {
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color backgroundColor: Colors.grey[400], // Light grey color
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
child: Text( child: Text(
@ -924,7 +932,6 @@ class _TrainScreenState extends State<TrainScreen> {
), ),
), ),
SizedBox(width: 10), // Space between buttons SizedBox(width: 10), // Space between buttons
// Save Changes Button // Save Changes Button
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
@ -932,9 +939,7 @@ class _TrainScreenState extends State<TrainScreen> {
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B), // Primary color for save backgroundColor: Color(0xFF114D8B), // Primary color for save
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
child: Text( child: Text(

View File

@ -245,14 +245,26 @@ class AccomodationListWidget extends StatelessWidget {
Spacer(), Spacer(),
GestureDetector( GestureDetector(
onTap: () => onOpen(true, item, "Accomodation"), onTap: () => onOpen(true, item, "Accomodation"),
child: Image.asset('assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, height: 15), message: 'Edit Accomodation Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
GestureDetector( GestureDetector(
onTap: () => onDeleteAccommodation(item), onTap: () => onDeleteAccommodation(item),
child: Image.asset('assets/images/IconsImg/delete.png', child: Tooltip(
width: 20, height: 15), message: 'Delete Accommodation Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
), ),
], ],
), ),

View File

@ -224,14 +224,26 @@ class BusListWidget extends StatelessWidget {
Spacer(), Spacer(),
GestureDetector( GestureDetector(
onTap: () => onOpen(true, item, "Bus"), onTap: () => onOpen(true, item, "Bus"),
child: Image.asset('assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, height: 15), message: 'Delete Edit Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
GestureDetector( GestureDetector(
onTap: () => onDeleteBus(item), onTap: () => onDeleteBus(item),
child: Image.asset('assets/images/IconsImg/delete.png', child: Tooltip(
width: 20, height: 15), message: 'Delete Bus Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
), ),
], ],
), ),

View File

@ -85,7 +85,8 @@ class _FlightListWidgetState extends State<FlightListWidget> {
return AlertDialog( return AlertDialog(
title: const Text('Select Trip Type'), title: const Text('Select Trip Type'),
content: const Text( content: const Text(
'Please select a trip type before adding a flight.'), 'Please select a trip type before adding a flight.',
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
@ -118,11 +119,13 @@ class _FlightListWidgetState extends State<FlightListWidget> {
// style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), // style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
// ), // ),
MouseRegion( MouseRegion(
cursor: widget.isViewMode cursor:
widget.isViewMode
? SystemMouseCursors.forbidden ? SystemMouseCursors.forbidden
: SystemMouseCursors.click, : SystemMouseCursors.click,
child: GestureDetector( child: GestureDetector(
onTap: widget.isViewMode onTap:
widget.isViewMode
? null ? null
: () { : () {
checkClass(); checkClass();
@ -141,7 +144,7 @@ class _FlightListWidgetState extends State<FlightListWidget> {
Icons.add_circle_sharp, Icons.add_circle_sharp,
size: 30, size: 30,
color: Color(0xFF114D8B), color: Color(0xFF114D8B),
) ),
], ],
), ),
), ),
@ -149,9 +152,9 @@ class _FlightListWidgetState extends State<FlightListWidget> {
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Scroll behavior based on device
_buildData(context, isDesktop) // Scroll behavior based on device
_buildData(context, isDesktop),
], ],
), ),
), ),
@ -224,8 +227,11 @@ class _FlightListWidgetState extends State<FlightListWidget> {
if (hour == 24 && minute == 0) { if (hour == 24 && minute == 0) {
// 24:00 is treated as 00:00 on the next day // 24:00 is treated as 00:00 on the next day
dateTime = DateTime(now.year, now.month, now.day) dateTime = DateTime(
.add(const Duration(days: 1)); now.year,
now.month,
now.day,
).add(const Duration(days: 1));
} else { } else {
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
throw FormatException("Invalid hour or minute"); throw FormatException("Invalid hour or minute");
@ -245,7 +251,21 @@ class _FlightListWidgetState extends State<FlightListWidget> {
itemCount: filteredList.length, itemCount: filteredList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = filteredList[index]; final item = filteredList[index];
String? tripTypeName = '';
switch (item["trip_type"]?.toString()) {
case "Roundtrip":
tripTypeName = "Round Trip";
break;
case "Multitrip":
tripTypeName = "Multi-Trip";
break;
case "Oneway":
tripTypeName = "One-Way";
break;
default:
tripTypeName = item["trip_type"]?.toString();
break;
}
return Container( return Container(
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 6), margin: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -281,7 +301,7 @@ class _FlightListWidgetState extends State<FlightListWidget> {
Row( Row(
children: [ children: [
Text( Text(
item["trip_type"]?.toString() ?? "N/A", tripTypeName ?? "N/A",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
@ -290,20 +310,30 @@ class _FlightListWidgetState extends State<FlightListWidget> {
Spacer(), Spacer(),
GestureDetector( GestureDetector(
onTap: () => widget.onOpen(true, item, "Flight"), onTap: () => widget.onOpen(true, item, "Flight"),
child: Image.asset('assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, height: 15), message: 'Edit Flight Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
GestureDetector( GestureDetector(
onTap: () => widget.onDeleteFlight(item), onTap: () => widget.onDeleteFlight(item),
child: Image.asset('assets/images/IconsImg/delete.png', child: Tooltip(
width: 20, height: 15), message: 'Delete Flight Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
), ),
], ],
), ),
Divider( Divider(color: Colors.blueGrey.shade50),
color: Colors.blueGrey.shade50,
),
SizedBox(height: 4), SizedBox(height: 4),
if (isDesktop) if (isDesktop)
Row( Row(
@ -312,39 +342,35 @@ class _FlightListWidgetState extends State<FlightListWidget> {
flex: 2, flex: 2,
child: Text( child: Text(
"Class", "Class",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 11),
fontSize: 11, ),
), ),
)),
Expanded( Expanded(
flex: 4, flex: 4,
child: Text( child: Text(
"Sector", "Sector",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 11),
fontSize: 11, ),
), ),
)),
Expanded( Expanded(
flex: 2, flex: 2,
child: Text( child: Text(
"Date", "Date",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 11),
fontSize: 11, ),
), ),
)),
Expanded( Expanded(
flex: 2, flex: 2,
child: Text( child: Text(
"Time", "Time",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 11),
fontSize: 11, ),
), ),
)),
], ],
), ),
SizedBox(height: 4), SizedBox(height: 4),
// Trip Rows
// Trip Rows
if (isDesktop) if (isDesktop)
if (item["trips"] != null && item["trips"].isNotEmpty) if (item["trips"] != null && item["trips"].isNotEmpty)
...item["trips"].map<Widget>((trip) { ...item["trips"].map<Widget>((trip) {
@ -373,7 +399,7 @@ class _FlightListWidgetState extends State<FlightListWidget> {
Expanded( Expanded(
flex: 4, flex: 4,
child: Text( child: Text(
"$fromPlaceCountry (from) - (to) $toPlaceCountry", "$fromPlaceCountry (From) - (To) $toPlaceCountry",
// "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}", // "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
@ -425,13 +451,20 @@ class _FlightListWidgetState extends State<FlightListWidget> {
_buildKeyValueRow( _buildKeyValueRow(
"Class", "Class",
getRequestForClass(trip["class"].toString()) ?? getRequestForClass(trip["class"].toString()) ??
"N/A"), "N/A",
_buildKeyValueRow("Sector", ),
"${trip["from_place"] ?? "N/A"} - ${trip["to_place"] ?? "N/A"}"),
_buildKeyValueRow( _buildKeyValueRow(
"Date", formatDate(trip["date"] ?? "")), "Sector",
"${trip["from_place"] ?? "N/A"} - ${trip["to_place"] ?? "N/A"}",
),
_buildKeyValueRow( _buildKeyValueRow(
"Time", formatTime(trip["time"] ?? "")), "Date",
formatDate(trip["date"] ?? ""),
),
_buildKeyValueRow(
"Time",
formatTime(trip["time"] ?? ""),
),
], ],
), ),
), ),
@ -462,12 +495,7 @@ class _FlightListWidgetState extends State<FlightListWidget> {
), ),
), ),
Expanded( Expanded(
child: Text( child: Text(value, style: GoogleFonts.poppins(fontSize: 12)),
value,
style: GoogleFonts.poppins(
fontSize: 12,
),
),
), ),
], ],
), ),

View File

@ -261,14 +261,26 @@ class ForexListWidget extends StatelessWidget {
Spacer(), Spacer(),
GestureDetector( GestureDetector(
onTap: () => onOpen(true, item, "Forex"), onTap: () => onOpen(true, item, "Forex"),
child: Image.asset('assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, height: 15), message: 'Edit Forex Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
GestureDetector( GestureDetector(
onTap: () => onDeleteForex(item), onTap: () => onDeleteForex(item),
child: Image.asset('assets/images/IconsImg/delete.png', child: Tooltip(
width: 20, height: 15), message: 'Delete Forex Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
), ),
item['forex_id'] != null item['forex_id'] != null
? IconButton( ? IconButton(

View File

@ -202,14 +202,26 @@ class InsuranceListWidget extends StatelessWidget {
Spacer(), Spacer(),
GestureDetector( GestureDetector(
onTap: () => onOpen(true, item, "Insurance"), onTap: () => onOpen(true, item, "Insurance"),
child: Image.asset('assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, height: 15), message: 'Edit Insurance Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
GestureDetector( GestureDetector(
onTap: () => onDeleteInsurance(item), onTap: () => onDeleteInsurance(item),
child: Image.asset('assets/images/IconsImg/delete.png', child: Tooltip(
width: 20, height: 15), message: 'Delete Insurance Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
), ),
], ],
), ),
@ -445,14 +457,26 @@ class InsuranceListWidget extends StatelessWidget {
children: [ children: [
GestureDetector( GestureDetector(
onTap: () => onOpen(true, item, "Insurance"), onTap: () => onOpen(true, item, "Insurance"),
child: Image.asset('assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, height: 15), message: 'Edit Insurance Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
GestureDetector( GestureDetector(
onTap: () => onDeleteInsurance(item), onTap: () => onDeleteInsurance(item),
child: Image.asset('assets/images/IconsImg/delete.png', child: Tooltip(
width: 20, height: 15), message: 'Delete Insurance Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
), ),
IconButton( IconButton(
icon: Icon(Icons.keyboard_arrow_down_outlined, icon: Icon(Icons.keyboard_arrow_down_outlined,

View File

@ -9,14 +9,15 @@ class MiscellaneousListWidget extends StatelessWidget {
final Function(String, bool) onAddNew; final Function(String, bool) onAddNew;
final bool isViewMode; final bool isViewMode;
const MiscellaneousListWidget( const MiscellaneousListWidget({
{super.key, super.key,
required this.miscellaneousList, required this.miscellaneousList,
required this.onOpen, required this.onOpen,
required this.onDeleteMiscellaneous, required this.onDeleteMiscellaneous,
required this.apiData, required this.apiData,
required this.onAddNew, required this.onAddNew,
required this.isViewMode}); required this.isViewMode,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -34,11 +35,13 @@ class MiscellaneousListWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
MouseRegion( MouseRegion(
cursor: isViewMode cursor:
isViewMode
? SystemMouseCursors.forbidden ? SystemMouseCursors.forbidden
: SystemMouseCursors.click, : SystemMouseCursors.click,
child: GestureDetector( child: GestureDetector(
onTap: isViewMode onTap:
isViewMode
? null ? null
: () { : () {
print("New data"); print("New data");
@ -57,7 +60,7 @@ class MiscellaneousListWidget extends StatelessWidget {
Icons.add_circle_sharp, Icons.add_circle_sharp,
size: 30, size: 30,
color: Color(0xFF114D8B), color: Color(0xFF114D8B),
) ),
], ],
), ),
), ),
@ -95,7 +98,7 @@ class MiscellaneousListWidget extends StatelessWidget {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Scroll behavior based on device // Scroll behavior based on device
_buildData(context, isDesktop) _buildData(context, isDesktop),
], ],
), ),
), ),
@ -162,20 +165,31 @@ class MiscellaneousListWidget extends StatelessWidget {
Spacer(), Spacer(),
GestureDetector( GestureDetector(
onTap: () => onOpen(true, item, "Miscellaneous"), onTap: () => onOpen(true, item, "Miscellaneous"),
child: Image.asset('assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, height: 15), message: 'Edit Miscellaneous Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
GestureDetector( GestureDetector(
onTap: () => onOpen(true, item, "Miscellaneous"), // onTap: () => onOpen(true, item, "Miscellaneous"),
child: Image.asset('assets/images/IconsImg/delete.png', onTap: () => onDeleteMiscellaneous(item),
width: 20, height: 15), child: Tooltip(
message: 'Delete Miscellaneous Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
), ),
], ],
), ),
Divider( Divider(color: Colors.blueGrey.shade50),
color: Colors.blueGrey.shade50,
),
SizedBox(height: 4), SizedBox(height: 4),
isDesktop isDesktop
? Row( ? Row(
@ -184,18 +198,16 @@ class MiscellaneousListWidget extends StatelessWidget {
flex: 2, flex: 2,
child: Text( child: Text(
"Special Request", "Special Request",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 11),
fontSize: 11, ),
), ),
)),
Expanded( Expanded(
flex: 3, flex: 3,
child: Text( child: Text(
" Comments", " Comments",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 11),
fontSize: 11, ),
), ),
)),
], ],
) )
: SizedBox.shrink(), : SizedBox.shrink(),
@ -206,7 +218,8 @@ class MiscellaneousListWidget extends StatelessWidget {
flex: 2, flex: 2,
child: Text( child: Text(
getRequestValue( getRequestValue(
item["special_request"]?.toString()), item["special_request"]?.toString(),
),
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -229,8 +242,8 @@ class MiscellaneousListWidget extends StatelessWidget {
children: [ children: [
_buildRow( _buildRow(
"Special Request:", "Special Request:",
getRequestValue( getRequestValue(item["special_request"]?.toString()),
item["special_request"]?.toString())), ),
SizedBox(width: 10), SizedBox(width: 10),
_buildRow("Comments:", item["comments"] ?? "N/A"), _buildRow("Comments:", item["comments"] ?? "N/A"),
], ],
@ -263,23 +276,27 @@ class MiscellaneousListWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( Expanded(
child: exceedsLimit child:
exceedsLimit
? Tooltip( ? Tooltip(
message: wrapText(value, 50), message: wrapText(value, 50),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey.shade200, // Black background color: Colors.grey.shade200, // Black background
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
textStyle: textStyle: TextStyle(
TextStyle(color: Colors.black), // Tooltip text color color: Colors.black,
), // Tooltip text color
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
preferBelow: false, preferBelow: false,
child: Text(value.substring(0, limit) + "...", child: Text(
value.substring(0, limit) + "...",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
overflow: TextOverflow.ellipsis), overflow: TextOverflow.ellipsis,
),
) )
: Text( : Text(
value, value,
@ -314,14 +331,12 @@ class MiscellaneousListWidget extends StatelessWidget {
children: [ children: [
Text( Text(
title, title,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600),
fontSize: 12,
fontWeight: FontWeight.w600,
),
), ),
SizedBox(width: 8), SizedBox(width: 8),
Expanded( Expanded(
child: exceedsLimit child:
exceedsLimit
? Tooltip( ? Tooltip(
message: wrapText(value, 50), message: wrapText(value, 50),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -329,7 +344,9 @@ class MiscellaneousListWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
textStyle: TextStyle( textStyle: TextStyle(
color: Colors.black, fontSize: 12), // Tooltip text color color: Colors.black,
fontSize: 12,
), // Tooltip text color
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
preferBelow: false, preferBelow: false,
child: Text( child: Text(
@ -343,7 +360,7 @@ class MiscellaneousListWidget extends StatelessWidget {
); );
} }
// //
// Widget _buildData(BuildContext context) { // Widget _buildData(BuildContext context) {
// return Container( // return Container(
// width: MediaQuery.of(context).size.width, // width: MediaQuery.of(context).size.width,

View File

@ -11,14 +11,15 @@ class TaxiListWidget extends StatelessWidget {
final Function(String, bool) onAddNew; final Function(String, bool) onAddNew;
final bool isViewMode; final bool isViewMode;
const TaxiListWidget( const TaxiListWidget({
{super.key, super.key,
required this.taxiList, required this.taxiList,
required this.apiData, required this.apiData,
required this.onOpen, required this.onOpen,
required this.onDeleteTaxi, required this.onDeleteTaxi,
required this.onAddNew, required this.onAddNew,
required this.isViewMode}); required this.isViewMode,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -36,12 +37,14 @@ class TaxiListWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
MouseRegion( MouseRegion(
cursor: isViewMode cursor:
isViewMode
? SystemMouseCursors.forbidden ? SystemMouseCursors.forbidden
: SystemMouseCursors.click, : SystemMouseCursors.click,
child: GestureDetector( child: GestureDetector(
onTap: isViewMode onTap:
isViewMode
? null ? null
: () { : () {
print("New data"); print("New data");
@ -60,7 +63,7 @@ class TaxiListWidget extends StatelessWidget {
Icons.add_circle_sharp, Icons.add_circle_sharp,
size: 30, size: 30,
color: Color(0xFF114D8B), color: Color(0xFF114D8B),
) ),
], ],
), ),
), ),
@ -99,9 +102,9 @@ class TaxiListWidget extends StatelessWidget {
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Scroll behavior based on device
_buildData(context, isDesktop) // Scroll behavior based on device
_buildData(context, isDesktop),
], ],
), ),
), ),
@ -187,8 +190,11 @@ class TaxiListWidget extends StatelessWidget {
if (hour == 24 && minute == 0) { if (hour == 24 && minute == 0) {
// 24:00 is treated as 00:00 on the next day // 24:00 is treated as 00:00 on the next day
dateTime = DateTime(now.year, now.month, now.day) dateTime = DateTime(
.add(const Duration(days: 1)); now.year,
now.month,
now.day,
).add(const Duration(days: 1));
} else { } else {
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
throw FormatException("Invalid hour or minute"); throw FormatException("Invalid hour or minute");
@ -242,24 +248,35 @@ class TaxiListWidget extends StatelessWidget {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
)), ),
),
Spacer(), Spacer(),
GestureDetector( GestureDetector(
onTap: () => onOpen(true, item, "Taxi"), onTap: () => onOpen(true, item, "Taxi"),
child: Image.asset('assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, height: 15), message: 'Edit Taxi Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
GestureDetector( GestureDetector(
onTap: () => (item), onTap: () => onDeleteTaxi(item),
child: Image.asset('assets/images/IconsImg/delete.png', child: Tooltip(
width: 20, height: 15), message: 'Delete Taxi Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
), ),
], ],
), ),
Divider( Divider(color: Colors.blueGrey.shade50),
color: Colors.blueGrey.shade50,
),
SizedBox(height: 4), SizedBox(height: 4),
isDesktop isDesktop
? Row( ? Row(
@ -268,34 +285,30 @@ class TaxiListWidget extends StatelessWidget {
flex: 2, flex: 2,
child: Text( child: Text(
"City", "City",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 11),
fontSize: 11, ),
), ),
)),
Expanded( Expanded(
flex: 2, flex: 2,
child: Text( child: Text(
" Location", " Location",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 11),
fontSize: 11, ),
), ),
)),
Expanded( Expanded(
flex: 2, flex: 2,
child: Text( child: Text(
" Date", " Date",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 11),
fontSize: 11, ),
), ),
)),
Expanded( Expanded(
flex: 2, flex: 2,
child: Text( child: Text(
"Comments", "Comments",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 11),
fontSize: 11, ),
), ),
)),
], ],
) )
: SizedBox.shrink(), : SizedBox.shrink(),
@ -339,7 +352,9 @@ class TaxiListWidget extends StatelessWidget {
Expanded( Expanded(
flex: 2, flex: 2,
child: _buildComments( child: _buildComments(
" Comments:", item["comments"] ?? "N/A"), " Comments:",
item["comments"] ?? "N/A",
),
), ),
], ],
) )
@ -358,7 +373,7 @@ class TaxiListWidget extends StatelessWidget {
_buildRow("TaxiFor:", item["car_required_for"]!), _buildRow("TaxiFor:", item["car_required_for"]!),
_buildRow("Comments:", item["comments"] ?? "N/A"), _buildRow("Comments:", item["comments"] ?? "N/A"),
], ],
) ),
], ],
), ),
), ),
@ -387,23 +402,27 @@ class TaxiListWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( Expanded(
child: exceedsLimit child:
exceedsLimit
? Tooltip( ? Tooltip(
message: wrapText(value, 50), message: wrapText(value, 50),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey.shade200, // Black background color: Colors.grey.shade200, // Black background
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
textStyle: textStyle: TextStyle(
TextStyle(color: Colors.black), // Tooltip text color color: Colors.black,
), // Tooltip text color
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
preferBelow: false, preferBelow: false,
child: Text(value.substring(0, limit) + "...", child: Text(
value.substring(0, limit) + "...",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
overflow: TextOverflow.ellipsis), overflow: TextOverflow.ellipsis,
),
) )
: Text( : Text(
value, value,
@ -438,34 +457,30 @@ class TaxiListWidget extends StatelessWidget {
children: [ children: [
Text( Text(
title, title,
style: TextStyle( style: TextStyle(fontWeight: FontWeight.w600, fontSize: 12),
fontWeight: FontWeight.w600,
fontSize: 12,
),
), ),
SizedBox(width: 8), SizedBox(width: 8),
Expanded( Expanded(
child: exceedsLimit child:
exceedsLimit
? Tooltip( ? Tooltip(
message: wrapText(value, 50), message: wrapText(value, 50),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey.shade200, // Black background color: Colors.grey.shade200, // Black background
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
textStyle: textStyle: TextStyle(
TextStyle(color: Colors.black), // Tooltip text color color: Colors.black,
), // Tooltip text color
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
preferBelow: false, preferBelow: false,
child: Text(value.substring(0, limit) + "...", child: Text(
value.substring(0, limit) + "...",
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
overflow: TextOverflow.ellipsis), overflow: TextOverflow.ellipsis,
),
) )
: Text( : Text(value, style: GoogleFonts.poppins(fontSize: 12)),
value,
style: GoogleFonts.poppins(
fontSize: 12,
),
),
), ),
], ],
); );
@ -477,19 +492,11 @@ class TaxiListWidget extends StatelessWidget {
children: [ children: [
Text( Text(
title, title,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600),
fontSize: 12,
fontWeight: FontWeight.w600,
),
), ),
SizedBox(width: 8), SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text("$date, $time", style: GoogleFonts.poppins(fontSize: 12)),
"$date, $time",
style: GoogleFonts.poppins(
fontSize: 12,
),
),
), ),
], ],
); );

View File

@ -333,14 +333,26 @@ class _TrainListWidgetState extends State<TrainListWidget> {
Spacer(), Spacer(),
GestureDetector( GestureDetector(
onTap: () => widget.onOpen(true, item, "Train"), onTap: () => widget.onOpen(true, item, "Train"),
child: Image.asset('assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, height: 15), message: 'Edit Train Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
GestureDetector( GestureDetector(
onTap: () => widget.onDeleteTrain(item), onTap: () => widget.onDeleteTrain(item),
child: Image.asset('assets/images/IconsImg/delete.png', child: Tooltip(
width: 20, height: 15), message: 'Delete Train Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
), ),
], ],
), ),

View File

@ -301,14 +301,26 @@ class VisaListWidget extends StatelessWidget {
Spacer(), Spacer(),
GestureDetector( GestureDetector(
onTap: () => onOpen(true, item, "Visa"), onTap: () => onOpen(true, item, "Visa"),
child: Image.asset('assets/images/IconsImg/edit.png', child: Tooltip(
width: 20, height: 15), message: 'Edit Visa Details',
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
), ),
SizedBox(width: 10), SizedBox(width: 10),
GestureDetector( GestureDetector(
onTap: () => onDeleteMiscellaneous(item), onTap: () => onDeleteMiscellaneous(item),
child: Image.asset('assets/images/IconsImg/delete.png', child: Tooltip(
width: 20, height: 15), message: 'Delete Visa Details',
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
), ),
], ],
), ),

View File

@ -0,0 +1,562 @@
import 'dart:convert';
import 'dart:io' as io show Directory, File;
import 'package:flutter/cupertino.dart' as dom;
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill/quill_delta.dart';
import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart';
import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart';
import 'package:flutter_quill/flutter_quill.dart' as quill;
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' as dom hide Element;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_quill/flutter_quill_internal.dart';
import 'package:flutter_quill/quill_delta.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
import 'package:responsive_builder/responsive_builder.dart';
import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_user_travel.dart';
class Template extends StatefulWidget {
final Map<String, dynamic>? templateData;
const Template({super.key, required this.templateData});
static Template fromState(GoRouterState state) {
return Template(templateData: state.extra as Map<String, dynamic>?);
}
@override
TemplateState createState() => TemplateState();
}
class TemplateState extends State<Template> {
final ApiService apiService = ApiService();
// final QuillController _controller = QuillController.basic();
String? orgId;
String? userId;
Color layoutColor = Colors.redAccent;
Color bodyColor = Colors.white;
final Map<String, TextEditingController> controllers = {};
List<String> dataHeader = ["subject"];
List<String> placeholders = [];
late QuillController _controller = QuillController.basic();
final FocusNode _focusNode = FocusNode();
late int templateId = 0;
late String templateName = "";
late List<Map<String, dynamic>> placeholderList = [];
Map<String, dynamic> get TemplateData {
final data = {
"org_id": orgId,
// "template_id": templateId,
// "template_name": controllers["templateName"]?.text,
"template_id": templateId,
"template_name": templateName,
"subject": controllers["subject"]?.text,
"body_html": jsonEncode(_controller.document.toDelta().toJson()),
// "body_html": _controller,
// "body_html": convertQuillDocToHtml(_controller.document),
// ✅ convert delta to HTML
"placeholder": jsonEncode(placeholderList),
// "created_by": userId
};
// Only add group_id if it's an edit operation
// if (widget.templateData != null && widget.templateData!.containsKey('group_id')) {
// data["template_id"] = templateData;
// }
return data;
}
@override
void initState() {
super.initState();
for (var field in dataHeader) {
controllers[field] = TextEditingController();
}
updateData();
loadinitializeData();
loadInitialData();
}
@override
// void dispose() {
// // controllers.dispose();
// // _editorScrollController.dispose();
// _editorFocusNode.dispose();
// super.dispose();
// }
void loadinitializeData() async {
orgId = await getOrgId();
userId = await getUserId();
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor();
setState(() {
layoutColor =
layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
});
}
// String convertQuillDocToHtml(quill.Document doc) {
// final buffer = StringBuffer();
//
// print("convertQuillDocToHtml");
// for (final op in doc.toDelta().toList()) {
// final insert = op.data;
// final attrs = op.attributes ?? {};
//
// if (insert is String) {
// var content = insert;
//
// // Handle formatting (bold, italic, etc.)
// if (attrs.containsKey('bold')) {
// content = '<strong>$content</strong>';
// }
// if (attrs.containsKey('italic')) {
// content = '<em>$content</em>';
// }
//
// // Wrap each paragraph with <p>
// if (content.trim().isNotEmpty) {
// buffer.write('<p>${content.trim()}</p>');
// }
// }
// }
//
// return buffer.toString();
// }
//
// String convertQuillDocToHtml2(quill.Document doc) {
// final buffer = StringBuffer();
// final lines = <String>[];
// final delta = doc.toDelta();
//
// String applyStyles(String text, Map<String, dynamic>? attrs) {
// if (attrs == null) return text;
// if (attrs.containsKey('bold')) {
// text = '<strong>$text</strong>';
// }
// if (attrs.containsKey('italic')) {
// text = '<em>$text</em>';
// }
// return text;
// }
//
// for (final op in delta.toList()) {
// final insert = op.data;
// final attrs = op.attributes;
//
// if (insert is String) {
// final parts = insert.split('\n');
// for (int i = 0; i < parts.length; i++) {
// final part = applyStyles(parts[i], attrs);
// lines.add(part);
//
// if (i < parts.length - 1) {
// // End of line: wrap accumulated content into <p>
// final joined = lines.join('');
// if (joined.trim().isNotEmpty) {
// buffer.writeln('<p>${joined.trim()}</p>');
// }
// lines.clear();
// }
// }
// }
// }
//
// // Add remaining lines
// final joined = lines.join('');
// if (joined.trim().isNotEmpty) {
// buffer.writeln('<p>${joined.trim()}</p>');
// }
//
// return buffer.toString();
// }
//
// String extractPlainTextFromHtml(String html) {
// final regex = RegExp(r'<p>(.*?)<\/p>', multiLine: true, dotAll: true);
// final matches = regex.allMatches(html);
//
// final buffer = StringBuffer();
// for (final match in matches) {
// final text = match.group(1)?.replaceAll(RegExp(r'<[^>]*>'), '') ?? '';
// buffer.writeln(text.trim());
// }
// return buffer.toString();
// }
//
// String decodeHtmlEntities(String text) {
// return text
// .replaceAll('&nbsp;', ' ')
// .replaceAll('&amp;', '&')
// .replaceAll('&lt;', '<')
// .replaceAll('&gt;', '>')
// .replaceAll('&quot;', '"')
// .replaceAll('&#39;', "'"); // add more as needed
// }
Future<void> updateData() async {
// Ensure apiselectedUser is not null before printing
if (widget.templateData != null) {
print("API Selected User Has Data - ${widget.templateData}");
print(
"API Selected User Has - ${widget.templateData?["templateData"]?["subject"]}",
);
setState(() {
// ✅ Wrap in setState to update the UI
controllers["templateName"]?.text =
widget.templateData?["templateData"]?["template_name"] ?? "";
controllers["subject"]?.text =
widget.templateData?["templateData"]?["subject"] ?? "";
final bodyHtml =
widget.templateData?["templateData"]?["body_html"] ?? "";
print("bodyHtml - $bodyHtml");
// /*inal converter = DeltaFromHTML();
// final delta = converter.convert(bodyHtml); // Convert HTML → Delta
// final quillDoc = quill.Document.fromDelta(delta);
// */
// final plainText = extractPlainTextFromHtml(bodyHtml);
// final decodedText = decodeHtmlEntities(plainText);
//
// final quillDoc = quill.Document()..insert(0, decodedText);
// // final quillDoc = convertBasicHtmlToQuill(bodyHtml);
// // final quillDoc = quill.Document()..insert(0, plainText);
// // final quillDoc = quill.Document.fromDelta(delta);
// _controller = quill.QuillController(
// document: quillDoc,
// selection: const TextSelection.collapsed(offset: 0),
// );
final deltaJsonString =
widget.templateData?["templateData"]?["body_delta"];
if (deltaJsonString != null) {
final deltaJson = jsonDecode(deltaJsonString);
final quillDoc = quill.Document.fromJson(deltaJson);
_controller = quill.QuillController(
document: quillDoc,
selection: const TextSelection.collapsed(offset: 0),
);
}
templateName =
widget.templateData?["templateData"]?["template_name"] ?? "";
print("Fetched template_name: $templateName");
final rawPlaceholder =
widget.templateData?["templateData"]?["placeholder"];
if (rawPlaceholder is String) {
// If it's a JSON string, decode it first
placeholderList = List<Map<String, dynamic>>.from(
jsonDecode(rawPlaceholder),
);
} else if (rawPlaceholder is List) {
// If it's already a list (ideal case)
placeholderList = List<Map<String, dynamic>>.from(rawPlaceholder);
}
print("Extracted placeholders: $placeholders");
print("Fetched placeholders: $placeholderList");
templateId =
int.tryParse(
widget.templateData?["templateData"]?["template_id"]
?.toString() ??
'0',
) ??
0;
print("Fetched template_id: $templateId");
// if (widget.group?["international_policy_id"] != null) {
// selectedInternational =
// widget.group!["international_policy_id"].toString();
// }
});
} else {
print("API Selected User Has Data - No data available yet");
}
}
Future<void> handleSubmit() async {
Map<String, dynamic> data = TemplateData;
setState(() {
// updateTemplateData(data);
// This triggers UI rebuild with error messages
// if (validateData()) {
// postGroupData();
// }
});
final TemplateData1 = TemplateData;
print("TemplateData - $TemplateData1");
}
Future<void> updateTemplateData(Map<String, dynamic> policyData) async {
final String apiUrldata = '$apiUrl/api/template/update/${templateId}';
final token = await getToken(); // Fetch token
if (token == null) {
throw Exception('Token not found. Please log in.');
}
try {
final response = await http.put(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode(policyData), // Convert map to JSON
);
if (response.statusCode == 200) {
print("policyData submitted successfully!");
print("Response: ${response.body}");
context.go('/templateList');
} else {
print("Failed to submit policyData. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting policyData: $e");
}
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: const Color(0xFFf5f5f5),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding:
isDesktop
? EdgeInsets.symmetric(
horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(
child: buildUserTable(
isDesktop,
context,
bodyColor,
layoutColor,
),
),
],
),
),
);
},
);
}
Widget buildUserTable(
bool isDesktop,
context,
Color? bodyColor,
Color layoutColor,
) {
return Container(
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
padding: const EdgeInsets.all(28),
decoration: BoxDecoration(
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Editor"),
SizedBox(height: 10),
buildTempalteSubject(isDesktop),
SizedBox(height: 10),
buildTempalteBody(isDesktop),
Spacer(),
buildActions(isDesktop),
],
),
),
);
}
Widget buildTempalteSubject(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Subject",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["subject"],
onChanged: (value) {
// _clearError("local_id_num");
},
decoration: InputDecoration(
labelText: "enter the subject",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildTempalteBody(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Content",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
const SizedBox(height: 10),
Container(child: QuillSimpleToolbar(controller: _controller)),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.all(16),
height: 200,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: QuillEditor(
controller: _controller,
scrollController: ScrollController(),
focusNode: _focusNode,
),
),
],
);
}
Widget buildActions(bool isDesktop) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
child: ElevatedButton(
onPressed: () {
context.go('/templateList');
// You can get text from commentController.text
Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
// backgroundColor: widget.layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
'Cancel',
style: GoogleFonts.poppins(fontSize: 13, color: Colors.white),
),
),
),
SizedBox(width: 10),
SizedBox(
child: ElevatedButton(
onPressed: () {
handleSubmit();
// You can get text from commentController.text
// Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
// backgroundColor: widget.layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
'Save',
style: GoogleFonts.poppins(fontSize: 13, color: Colors.white),
),
),
),
],
);
}
}

View File

@ -1,4 +1,5 @@
const kScreenshot1 = 'assets/images/screenshot_1.png'; const kScreenshot1 = 'assets/images/screenshot_1.png';
const kScreenshot2 = 'assets/images/screenshot_2.png'; const kScreenshot2 = 'assets/images/screenshot_2.png';
const kScreenshot3 = 'assets/images/screenshot_3.png'; const kScreenshot3 = 'assets/images/screenshot_3.png';
const kScreenshot4 = 'assets/images/screenshot_4.png'; const kScreenshot4 =
'assets/images/screenshot_4.png'; // TODO Implement this library.

View File

@ -16,14 +16,8 @@ class CustomToolbar extends StatelessWidget {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Wrap( child: Wrap(
children: [ children: [
QuillToolbarHistoryButton( QuillToolbarHistoryButton(isUndo: true, controller: controller),
isUndo: true, QuillToolbarHistoryButton(isUndo: false, controller: controller),
controller: controller,
),
QuillToolbarHistoryButton(
isUndo: false,
controller: controller,
),
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
options: const QuillToolbarToggleStyleButtonOptions(), options: const QuillToolbarToggleStyleButtonOptions(),
controller: controller, controller: controller,
@ -38,40 +32,22 @@ class CustomToolbar extends StatelessWidget {
controller: controller, controller: controller,
attribute: Attribute.underline, attribute: Attribute.underline,
), ),
QuillToolbarClearFormatButton( QuillToolbarClearFormatButton(controller: controller),
controller: controller,
),
const VerticalDivider(), const VerticalDivider(),
QuillToolbarImageButton( QuillToolbarImageButton(controller: controller),
controller: controller, QuillToolbarCameraButton(controller: controller),
), QuillToolbarVideoButton(controller: controller),
QuillToolbarCameraButton(
controller: controller,
),
QuillToolbarVideoButton(
controller: controller,
),
const VerticalDivider(), const VerticalDivider(),
QuillToolbarColorButton( QuillToolbarColorButton(controller: controller, isBackground: false),
controller: controller, QuillToolbarColorButton(controller: controller, isBackground: true),
isBackground: false,
),
QuillToolbarColorButton(
controller: controller,
isBackground: true,
),
const VerticalDivider(), const VerticalDivider(),
QuillToolbarSelectHeaderStyleDropdownButton( QuillToolbarSelectHeaderStyleDropdownButton(controller: controller),
controller: controller,
),
const VerticalDivider(), const VerticalDivider(),
QuillToolbarSelectLineHeightStyleDropdownButton( QuillToolbarSelectLineHeightStyleDropdownButton(
controller: controller, controller: controller,
), ),
const VerticalDivider(), const VerticalDivider(),
QuillToolbarToggleCheckListButton( QuillToolbarToggleCheckListButton(controller: controller),
controller: controller,
),
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
controller: controller, controller: controller,
attribute: Attribute.ol, attribute: Attribute.ol,
@ -88,14 +64,8 @@ class CustomToolbar extends StatelessWidget {
controller: controller, controller: controller,
attribute: Attribute.blockQuote, attribute: Attribute.blockQuote,
), ),
QuillToolbarIndentButton( QuillToolbarIndentButton(controller: controller, isIncrease: true),
controller: controller, QuillToolbarIndentButton(controller: controller, isIncrease: false),
isIncrease: true,
),
QuillToolbarIndentButton(
controller: controller,
isIncrease: false,
),
const VerticalDivider(), const VerticalDivider(),
QuillToolbarLinkStyleButton(controller: controller), QuillToolbarLinkStyleButton(controller: controller),
], ],

View File

@ -0,0 +1,88 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill_internal.dart';
import 'package:frontend/utils/auth_utils.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
class PlaceholdersModal extends StatefulWidget {
final List<Map<String, dynamic>> placeholders;
const PlaceholdersModal({Key? key, required this.placeholders})
: super(key: key);
@override
_PlaceholdersModalState createState() => _PlaceholdersModalState();
}
class _PlaceholdersModalState extends State<PlaceholdersModal> {
@override
Widget build(BuildContext context) {
String templLabel(String placeholder) {
var label = placeholder.replaceAll('%', '').replaceAll('_', ' ');
return label
.split(' ')
.map(
(word) =>
word.isNotEmpty
? word[0].toUpperCase() + word.substring(1)
: '',
)
.join(' ');
}
return AlertDialog(
backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Text(
"Available Placeholders",
style: GoogleFonts.poppins(fontSize: 15, fontWeight: FontWeight.w500),
),
content: Container(
width:
isDesktop
? MediaQuery.of(context).size.width * 0.4
: double.maxFinite,
// Set max height so ListView knows constraints
height: 300,
child: ListView.builder(
shrinkWrap: true,
itemCount: widget.placeholders.length,
itemBuilder: (context, index) {
final value = widget.placeholders[index]['value'] ?? '';
return ListTile(
hoverColor: Colors.white,
focusColor: Colors.white,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
templLabel(value),
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
SelectableText(
value,
style: GoogleFonts.poppins(fontSize: 12),
),
],
),
// onTap: () {
// Navigator.of(context).pop(value);
// },
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text("Close", style: GoogleFonts.poppins(fontSize: 12)),
),
],
);
}
}

View File

@ -1,30 +0,0 @@
import 'package:flutter_quill/flutter_quill.dart';
import 'package:meta/meta.dart';
import '../editor/image/image_embed_types.dart';
import 'extensions/controller_ext.dart';
OnImageInsertCallback _defaultOnImageInsert() {
return (imageUrl, controller) async {
controller
..skipRequestKeyboard = true
// ignore: deprecated_member_use_from_same_package
..insertImageBlock(imageSource: imageUrl);
};
}
@internal
Future<void> handleImageInsert(
String imageUrl, {
required QuillController controller,
required OnImageInsertCallback? onImageInsertCallback,
required OnImageInsertedCallback? onImageInsertedCallback,
}) async {
final customOnImageInsert = onImageInsertCallback;
if (customOnImageInsert != null) {
await customOnImageInsert.call(imageUrl, controller);
} else {
await _defaultOnImageInsert().call(imageUrl, controller);
}
await onImageInsertedCallback?.call(imageUrl);
}

View File

@ -1,30 +0,0 @@
import 'package:flutter_quill/flutter_quill.dart';
import 'package:meta/meta.dart';
import '../toolbar/video/config/video.dart';
import 'extensions/controller_ext.dart';
OnVideoInsertCallback _defaultOnVideoInsert() {
return (imageUrl, controller) async {
controller
..skipRequestKeyboard = true
// ignore: deprecated_member_use_from_same_package
..insertVideoBlock(videoUrl: imageUrl);
};
}
@internal
Future<void> handleVideoInsert(
String videoUrl, {
required QuillController controller,
required OnVideoInsertCallback? onVideoInsertCallback,
required OnVideoInsertedCallback? onVideoInsertedCallback,
}) async {
final customOnVideoInsert = onVideoInsertCallback;
if (customOnVideoInsert != null) {
await customOnVideoInsert.call(videoUrl, controller);
} else {
await _defaultOnVideoInsert().call(videoUrl, controller);
}
await onVideoInsertedCallback?.call(videoUrl);
}

View File

@ -1,12 +0,0 @@
import 'package:flutter_quill/flutter_quill.dart'
show Attribute, AttributeScope;
class FlutterAlignmentAttribute extends Attribute<String?> {
const FlutterAlignmentAttribute(String? val)
: super('flutterAlignment', AttributeScope.ignore, val);
}
extension AttributeExt on Attribute {
static const FlutterAlignmentAttribute flutterAlignment =
FlutterAlignmentAttribute(null);
}

View File

@ -1,36 +1 @@
import 'package:flutter_quill/flutter_quill.dart'; // TODO Implement this library.
@Deprecated('Invalid extension')
extension QuillControllerExt on QuillController {
@Deprecated(
'Invalid extension property and will be removed, use selection.baseOffset instead')
int get index => selection.baseOffset;
@Deprecated(
'Invalid extension property and will be removed, use selection.extentOffset - selection.baseOffset instead')
int get length => selection.extentOffset - index;
@Deprecated('Invalid extension method and will be removed.')
void insertImageBlock({
required String imageSource,
}) {
this
..skipRequestKeyboard = true
..replaceText(
index,
length,
BlockEmbed.image(imageSource),
null,
)
..moveCursorToPosition(index + 1);
}
@Deprecated('Invalid extension method and will be removed.')
void insertVideoBlock({
required String videoUrl,
}) {
this
..skipRequestKeyboard = true
..replaceText(index, length, BlockEmbed.video(videoUrl), null)
..moveCursorToPosition(index + 1);
}
}

View File

@ -1,122 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' show QuillDialogTheme;
import 'package:flutter_quill/internal.dart';
import 'utils/patterns.dart';
enum LinkType {
video,
image,
}
class TypeLinkDialog extends StatefulWidget {
const TypeLinkDialog({
required this.linkType,
this.dialogTheme,
this.link,
this.linkRegExp,
super.key,
});
final QuillDialogTheme? dialogTheme;
final String? link;
final RegExp? linkRegExp;
final LinkType linkType;
@override
TypeLinkDialogState createState() => TypeLinkDialogState();
}
class TypeLinkDialogState extends State<TypeLinkDialog> {
late String _link;
late TextEditingController _controller;
RegExp? _linkRegExp;
@override
void initState() {
super.initState();
_link = widget.link ?? '';
_controller = TextEditingController(text: _link);
_linkRegExp = widget.linkRegExp;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: widget.dialogTheme?.dialogBackgroundColor,
content: TextField(
keyboardType: TextInputType.url,
textInputAction: TextInputAction.done,
maxLines: null,
style: widget.dialogTheme?.inputTextStyle,
decoration: InputDecoration(
labelText: context.loc.pasteLink,
hintText: widget.linkType == LinkType.image
? context.loc.pleaseEnterAValidImageURL
: context.loc.pleaseEnterAValidVideoURL,
labelStyle: widget.dialogTheme?.labelTextStyle,
floatingLabelStyle: widget.dialogTheme?.labelTextStyle,
),
autofocus: true,
onChanged: _linkChanged,
controller: _controller,
onEditingComplete: () {
if (!_canPress()) {
return;
}
_applyLink();
},
),
actions: [
TextButton(
onPressed: _canPress() ? _applyLink : null,
child: Text(
context.loc.ok,
style: widget.dialogTheme?.labelTextStyle,
),
),
],
);
}
void _linkChanged(String value) {
setState(() {
_link = value;
});
}
void _applyLink() {
Navigator.pop(context, _link.trim());
}
RegExp get linkRegExp {
final customRegExp = _linkRegExp;
if (customRegExp != null) {
return customRegExp;
}
switch (widget.linkType) {
case LinkType.video:
if (youtubeRegExp.hasMatch(_link)) {
return youtubeRegExp;
}
return videoRegExp;
case LinkType.image:
return imageRegExp;
}
}
bool _canPress() {
if (_link.isEmpty) {
return false;
}
if (widget.linkType == LinkType.image) {}
return _link.isNotEmpty && linkRegExp.hasMatch(_link);
}
}

View File

@ -1,43 +0,0 @@
// import 'package:universal_html/html.dart' as html;
// Fake interface for the logic that this package needs from (web-only) dart:ui.
// This is conditionally exported so the analyzer sees these methods as
// available.
// typedef PlatroformViewFactory = html.Element Function(int viewId);
// /// Shim for web_ui engine.PlatformViewRegistry
// /// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L62
// class PlatformViewRegistry {
// /// Shim for registerViewFactory
// /// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L72
// static dynamic registerViewFactory(
// String viewTypeId, PlatroformViewFactory viewFactory) {}
// }
// /// Shim for web_ui engine.AssetManager
// /// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/assets.dart#L12
// class WebOnlyAssetManager {
// static dynamic getAssetUrl(String asset) {}
// }
class PlatformViewRegistry {
/// Register [viewType] as being created by the given [viewFactory].
///
/// [viewFactory] can be any function that takes an integer and optional
/// `params` and returns an `HTMLElement` DOM object.
bool registerViewFactory(
String viewType,
Function viewFactory, {
bool isVisible = true,
}) {
return false;
}
/// Returns the view previously created for [viewId].
///
/// Throws if no view has been created for [viewId].
Object getViewById(int viewId) {
return '';
}
}

View File

@ -1 +0,0 @@
export 'dart:ui' if (dart.library.js_interop) 'dart:ui_web';

View File

@ -1,84 +0,0 @@
import 'package:flutter/widgets.dart' show BuildContext, MediaQuery;
Map<String, String> parseCssString(String cssString) {
final result = <String, String>{};
final declarations = cssString.split(';');
for (final declaration in declarations) {
final parts = declaration.split(':');
if (parts.length == 2) {
final property = parts[0].trim();
final value = parts[1].trim();
result[property] = value;
}
}
return result;
}
enum _CssUnit {
px('px'),
percentage('%'),
viewportWidth('vw'),
viewportHeight('vh'),
em('em'),
rem('rem'),
invalid('invalid');
const _CssUnit(this.cssName);
final String cssName;
}
double? parseCssPropertyAsDouble(
String value, {
required BuildContext context,
}) {
if (value.trim().isEmpty) {
return null;
}
// Try to parse it in case it's a valid double already
var doubleValue = double.tryParse(value);
if (doubleValue != null) {
return doubleValue;
}
// If not then if it's a css numberic value then we will try to parse it
final unit = _CssUnit.values
.where((element) => value.endsWith(element.cssName))
.firstOrNull;
if (unit == null) {
return null;
}
value = value.replaceFirst(unit.cssName, '');
doubleValue = double.tryParse(value);
if (doubleValue != null) {
switch (unit) {
case _CssUnit.px:
// Do nothing
break;
case _CssUnit.percentage:
// Not supported yet
doubleValue = null;
break;
case _CssUnit.viewportWidth:
doubleValue = (doubleValue / 100) * MediaQuery.sizeOf(context).width;
break;
case _CssUnit.viewportHeight:
doubleValue = (doubleValue / 100) * MediaQuery.sizeOf(context).height;
break;
case _CssUnit.em:
doubleValue = MediaQuery.textScalerOf(context).scale(doubleValue);
break;
case _CssUnit.rem:
doubleValue = MediaQuery.textScalerOf(context).scale(doubleValue);
break;
case _CssUnit.invalid:
doubleValue = null;
break;
}
}
return doubleValue;
}

View File

@ -1,106 +0,0 @@
import 'package:flutter/foundation.dart' show immutable;
import 'package:flutter/widgets.dart' show Alignment, BuildContext;
import 'package:flutter_quill/flutter_quill.dart' show Attribute, Node;
import 'package:flutter_quill/internal.dart';
import 'element_shared_utils.dart';
/// Theses properties are not officialy supported by quill js
/// but they are only used in all platforms other than web
/// and they will be stored in css style property so quill js ignore them
enum ExtraElementProperties {
deletable,
}
(
ElementSize elementSize,
double? margin,
Alignment alignment,
) getElementAttributes(
Node node,
BuildContext context,
) {
var elementSize = const ElementSize(null, null);
var elementAlignment = Alignment.center;
double? elementMargin;
final heightValue = parseCssPropertyAsDouble(
node.style.attributes[Attribute.height.key]?.value.toString() ?? '',
context: context,
);
final widthValue = parseCssPropertyAsDouble(
node.style.attributes[Attribute.width.key]?.value.toString() ?? '',
context: context,
);
if (heightValue != null) {
elementSize = elementSize.copyWith(
height: heightValue,
);
}
if (widthValue != null) {
elementSize = elementSize.copyWith(
width: widthValue,
);
}
final cssStyle = node.style.attributes['style'];
if (cssStyle != null) {
// It css value as string but we will try to support it anyway
final cssAttrs = parseCssString(cssStyle.value.toString());
final cssHeightValue = parseCssPropertyAsDouble(
(cssAttrs[Attribute.height.key]) ?? '',
context: context,
);
final cssWidthValue = parseCssPropertyAsDouble(
(cssAttrs[Attribute.width.key]) ?? '',
context: context,
);
// cssHeightValue != null && elementSize.height == null
if (cssHeightValue != null) {
elementSize = elementSize.copyWith(height: cssHeightValue);
}
if (cssWidthValue != null) {
elementSize = elementSize.copyWith(width: cssWidthValue);
}
elementAlignment = getAlignment(cssAttrs['alignment']);
final margin = double.tryParse('margin');
if (margin != null) {
elementMargin = margin;
}
}
return (elementSize, elementMargin, elementAlignment);
}
@immutable
class ElementSize {
const ElementSize(
this.width,
this.height,
);
/// If non-null, requires the child to have exactly this width.
/// If null, the child is free to choose its own width.
final double? width;
/// If non-null, requires the child to have exactly this height.
/// If null, the child is free to choose its own height.
final double? height;
ElementSize copyWith({
double? width,
double? height,
}) {
return ElementSize(
width ?? this.width,
height ?? this.height,
);
}
}

View File

@ -1,60 +0,0 @@
import 'package:flutter_quill/flutter_quill.dart' show Attribute, Node;
import 'element_shared_utils.dart';
/// Prefer the width, and height from the css style attribute if exits
/// it can be `auto` or `100px` so it's specific to HTML && CSS
/// if not, we will use the one from attributes which is usually just an double
(
String height,
String width,
String margin,
String alignment,
) getWebElementAttributes(
Node node,
) {
var height = 'auto';
var width = 'auto';
// TODO(): Add support for margin and alignment
var margin = 'auto';
const alignment = 'center';
final cssStyle = node.style.attributes['style'];
final heightValue = node.style.attributes[Attribute.height.key]?.value;
final widthValue = node.style.attributes[Attribute.width.key]?.value;
if (cssStyle != null) {
final attrs = parseCssString(cssStyle.value.toString());
final cssHeightValue = attrs[Attribute.height.key];
if (cssHeightValue != null) {
height = cssHeightValue;
} else {
height = '${heightValue}px';
}
final cssWidthValue = attrs[Attribute.width.key];
if (cssWidthValue != null) {
width = cssWidthValue;
} else if (widthValue != null) {
width = '${widthValue}px';
}
final cssMarginValue = attrs['margin'];
if (cssMarginValue != null) {
margin = cssMarginValue;
}
return (height, width, margin, alignment);
}
if (heightValue != null) {
height = '${heightValue}px';
}
if (widthValue != null) {
width = '${widthValue}px';
}
return (height, width, margin, alignment);
}

View File

@ -1,17 +0,0 @@
RegExp base64RegExp = RegExp(
r'^(?:[A-Za-z0-9+\/][A-Za-z0-9+\/][A-Za-z0-9+\/][A-Za-z0-9+\/])*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$',
);
final imageRegExp = RegExp(
r'https?://.*?\.(?:png|jpe?g|gif|bmp|webp|tiff?)',
caseSensitive: false,
);
final videoRegExp = RegExp(
r'\bhttps?://\S+\.(mp4|mov|avi|mkv|flv|wmv|webm)\b',
caseSensitive: false,
);
final youtubeRegExp = RegExp(
r'^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|live\/|v\/)?)([\w\-]+)(\S+)?$',
caseSensitive: false,
);

View File

@ -1,30 +0,0 @@
import 'package:flutter_quill/flutter_quill.dart' show Attribute;
String replaceStyleStringWithSize(
String cssStyle, {
required double width,
required double height,
}) {
final result = <String, String>{};
final pairs = cssStyle.split(';');
for (final pair in pairs) {
final index = pair.indexOf(':');
if (index < 0) {
continue;
}
final key = pair.substring(0, index).trim();
result[key] = pair.substring(index + 1).trim();
}
result[Attribute.width.key] = width.toString();
result[Attribute.height.key] = height.toString();
final sb = StringBuffer();
for (final pair in result.entries) {
sb
..write(pair.key)
..write(': ')
..write(pair.value)
..write('; ');
}
return sb.toString();
}

View File

@ -1,30 +0,0 @@
import 'patterns.dart';
bool isBase64(String str) {
return base64RegExp.hasMatch(str);
}
bool isHttpUrl(String url) {
try {
final uri = Uri.parse(url.trim());
return uri.isScheme('HTTP') || uri.isScheme('HTTPS');
} catch (_) {
return false;
}
}
bool isImageBase64(String imageUrl) {
return !isHttpUrl(imageUrl) && isBase64(imageUrl);
}
bool isYouTubeUrl(String videoUrl) {
try {
final uri = Uri.parse(videoUrl);
return uri.host == 'www.youtube.com' ||
uri.host == 'youtube.com' ||
uri.host == 'youtu.be' ||
uri.host == 'www.youtu.be';
} catch (_) {
return false;
}
}

View File

@ -1 +0,0 @@
export './web_stub.dart' if (dart.library.js_interop) './web_real.dart';

View File

@ -1,46 +0,0 @@
import 'package:web/web.dart';
import '../dart_ui/dart_ui_fake.dart'
if (dart.library.js_interop) '../dart_ui/dart_ui_real.dart' as ui;
void main(List<String> args) {
HTMLImageElement;
}
void createHtmlImageElement({
required String src,
required String height,
required String width,
required String margin,
required String alignSelf,
}) {
ui.PlatformViewRegistry().registerViewFactory(src, (viewId) {
return createHtmlImageElement(
src: src,
alignSelf: alignSelf,
width: width,
height: height,
margin: margin,
);
});
}
void createHtmlIFrameElement({
required String src,
required String height,
required String width,
required String margin,
required String alignSelf,
}) {
ui.PlatformViewRegistry().registerViewFactory(
src,
(id) {
return HTMLIFrameElement()
..style.width = width
..style.height = height
..src = src
..style.border = 'none'
..style.margin = margin
..style.alignSelf = alignSelf;
},
);
}

View File

@ -1,19 +0,0 @@
void createHtmlImageElement({
required String src,
required String height,
required String width,
required String margin,
required String alignSelf,
}) =>
throw UnimplementedError(
'A stub method is called, createHtmlImageElement is for web platforms only.');
void createHtmlIFrameElement({
required String src,
required String height,
required String width,
required String margin,
required String alignSelf,
}) =>
throw UnimplementedError(
'A stub method is called, createHtmlIFrameElement is for web platforms only.');

View File

@ -1,165 +1 @@
import 'dart:io' show File; // TODO Implement this library.
import 'package:flutter/foundation.dart';
import 'package:flutter_quill/internal.dart';
import '../image_embed_types.dart';
/// [QuillEditorImageEmbedConfig] for desktop, mobile and
/// other platforms
/// excluding web, it's configurations that is needed for the editor
///
@immutable
class QuillEditorImageEmbedConfig {
const QuillEditorImageEmbedConfig({
ImageEmbedBuilderOnRemovedCallback? onImageRemovedCallback,
this.shouldRemoveImageCallback,
this.imageProviderBuilder,
this.imageErrorWidgetBuilder,
this.onImageClicked,
}) : _onImageRemovedCallback = onImageRemovedCallback;
/// [onImageRemovedCallback] is called when an image is
/// removed from the editor.
/// By default, [onImageRemovedCallback] deletes the
/// temporary image file if
/// the platform is mobile and if it still exists. You
/// can customize this behavior
/// by passing your own function that handles the removal process.
///
/// Example of [onImageRemovedCallback] customization:
/// ```dart
/// afterRemoveImageFromEditor: (imageFile) async {
/// // Your custom logic here
/// // or leave it empty to do nothing
/// }
/// ```
///
/// Default value if the passed value is null:
/// [QuillEditorImageEmbedConfig.defaultOnImageRemovedCallback]
///
/// so if you want to do nothing make sure to pass a empty callback
/// instead of passing null as value
final ImageEmbedBuilderOnRemovedCallback? _onImageRemovedCallback;
ImageEmbedBuilderOnRemovedCallback get onImageRemovedCallback {
return _onImageRemovedCallback ??
QuillEditorImageEmbedConfig.defaultOnImageRemovedCallback;
}
/// [shouldRemoveImageCallback] is a callback
/// function that is invoked when the
/// user attempts to remove an image from the editor. It allows you to control
/// whether the image should be removed based on your custom logic.
///
/// Example of [shouldRemoveImageCallback] customization:
/// ```dart
/// shouldRemoveImageFromEditor: (imageFile) async {
/// // Show a confirmation dialog before removing the image
/// final isShouldRemove = await showYesCancelDialog(
/// context: context,
/// options: const YesOrCancelDialogOptions(
/// title: 'Deleting an image',
/// message: 'Are you sure you want' ' to delete this
/// image from the editor?',
/// ),
/// );
///
/// // Return `true` to allow image removal if the user confirms, otherwise
/// `false`
/// return isShouldRemove;
/// }
/// ```
///
final ImageEmbedBuilderWillRemoveCallback? shouldRemoveImageCallback;
/// Allows to override the default handling and fallback to the default if `null` was returned.
///
/// Example of [imageProviderBuilder] customization:
/// ```dart
/// imageProviderBuilder: (imageUrl) async {
/// if (imageUrl.startsWith('assets/')) {
/// // Supports Image assets
/// return AssetImage(imageUrl);
/// }
/// if (imageUrl.startsWith('http')) {
/// // Use https://pub.dev/packages/cached_network_image
/// // for network images to cache them.
/// return CachedNetworkImageProvider(imageUrl);
/// }
///
/// // Return null to fallback to default handling
/// return null;
/// }
/// ```
///
final ImageEmbedBuilderProviderBuilder? imageProviderBuilder;
/// [imageErrorWidgetBuilder] if you want to show a custom widget based on the
/// exception that happen while loading the image, if it network image or
/// local one, and it will get called on all the images even in the photo
/// preview widget and not just in the quill editor
/// by default the default error from flutter framework will thrown
///
final ImageEmbedBuilderErrorWidgetBuilder? imageErrorWidgetBuilder;
/// What should happen when the image is pressed?
///
/// By default will show `ImageOptionsMenu` dialog. If you want to handle what happens
/// to the image when it's clicked, you can pass a callback to this property.
final void Function(String imageSource)? onImageClicked;
static ImageEmbedBuilderOnRemovedCallback get defaultOnImageRemovedCallback {
return (imageUrl) async {
if (kIsWeb) {
return;
}
final mobile = isMobileApp;
// If the platform is not mobile, return void;
// Since the mobile OS gives us a copy of the image
// Note: We should remove the image on Flutter web
// since the behavior is similar to how it is on mobile,
// but since this builder is not for web, we will ignore it
if (!mobile) {
return;
}
// On mobile OS (Android, iOS), the system will not give us
// direct access to the image; instead,
// it will give us the image
// in the temp directory of the application. So, we want to
// remove it when we no longer need it.
// but on desktop we don't want to touch user files
// especially on macOS, where we can't even delete
// it without
// permission
final dartIoImageFile = File(imageUrl);
final isFileExists = await dartIoImageFile.exists();
if (isFileExists) {
await dartIoImageFile.delete();
}
};
}
QuillEditorImageEmbedConfig copyWith({
ImageEmbedBuilderOnRemovedCallback? onImageRemovedCallback,
ImageEmbedBuilderWillRemoveCallback? shouldRemoveImageCallback,
ImageEmbedBuilderProviderBuilder? imageProviderBuilder,
ImageEmbedBuilderErrorWidgetBuilder? imageErrorWidgetBuilder,
bool? forceUseMobileOptionMenuForImageClick,
}) {
return QuillEditorImageEmbedConfig(
onImageRemovedCallback: onImageRemovedCallback ?? _onImageRemovedCallback,
shouldRemoveImageCallback:
shouldRemoveImageCallback ?? this.shouldRemoveImageCallback,
imageProviderBuilder: imageProviderBuilder ?? this.imageProviderBuilder,
imageErrorWidgetBuilder:
imageErrorWidgetBuilder ?? this.imageErrorWidgetBuilder,
);
}
}

View File

@ -1,11 +1 @@
import 'package:flutter/widgets.dart' show BoxConstraints; // TODO Implement this library.
import 'package:meta/meta.dart' show immutable;
@immutable
class QuillEditorWebImageEmbedConfig {
const QuillEditorWebImageEmbedConfig({
this.constraints,
});
final BoxConstraints? constraints;
}

View File

@ -1,77 +1 @@
import 'package:flutter/material.dart'; // TODO Implement this library.
import 'package:flutter_quill/flutter_quill.dart';
import '../../common/utils/element_utils/element_utils.dart';
import 'config/image_config.dart';
import 'image_menu.dart';
import 'widgets/image.dart';
class QuillEditorImageEmbedBuilder extends EmbedBuilder {
QuillEditorImageEmbedBuilder({
required this.config,
});
final QuillEditorImageEmbedConfig config;
@override
String get key => BlockEmbed.imageType;
@override
bool get expanded => false;
@override
Widget build(
BuildContext context,
EmbedContext embedContext,
) {
final imageSource = standardizeImageUrl(embedContext.node.value.data);
final ((imageSize), margin, alignment) = getElementAttributes(
embedContext.node,
context,
);
final width = imageSize.width;
final height = imageSize.height;
final imageWidget = getImageWidgetByImageSource(
context: context,
imageSource,
imageProviderBuilder: config.imageProviderBuilder,
imageErrorWidgetBuilder: config.imageErrorWidgetBuilder,
alignment: alignment,
height: height,
width: width,
);
return GestureDetector(
onTap: () {
final onImageClicked = config.onImageClicked;
if (onImageClicked != null) {
onImageClicked(imageSource);
return;
}
showDialog(
context: context,
builder: (_) => ImageOptionsMenu(
controller: embedContext.controller,
config: config,
imageSource: imageSource,
imageSize: imageSize,
readOnly: embedContext.readOnly,
imageProvider: imageWidget.image,
),
);
},
child: Builder(
builder: (context) {
if (margin != null) {
return Padding(
padding: EdgeInsets.all(margin),
child: imageWidget,
);
}
return imageWidget;
},
),
);
}
}

View File

@ -1,67 +1 @@
import 'package:flutter/widgets.dart' // TODO Implement this library.
show ImageErrorWidgetBuilder, ImageProvider;
import 'package:flutter/widgets.dart' show BuildContext;
import 'package:flutter_quill/flutter_quill.dart';
import 'package:meta/meta.dart' show immutable;
/// When request picking an image, for example when the image button toolbar
/// clicked, it should be null in case the user didn't choose any image or
/// any other reasons, and it should be the image file path as string that is
/// exists in case the user picked the image successfully
///
/// by default we already have a default implementation that show a dialog
/// request the source for picking the image, from gallery, link or camera
typedef OnRequestPickImage = Future<String?> Function(
BuildContext context,
);
/// A callback will called when inserting a image in the editor
/// it have the logic that will insert the image block using the controller
typedef OnImageInsertCallback = Future<void> Function(
String image,
QuillController controller,
);
/// When a new image picked this callback will called and you might want to
/// do some logic depending on your use case
typedef OnImageInsertedCallback = Future<void> Function(
String image,
);
enum InsertImageSource {
gallery,
camera,
link,
}
/// Configurations for dealing with images, on insert a image
/// on request picking a image
@immutable
class QuillToolbarImageConfig {
const QuillToolbarImageConfig({
this.onRequestPickImage,
this.onImageInsertedCallback,
this.onImageInsertCallback,
});
final OnRequestPickImage? onRequestPickImage;
final OnImageInsertedCallback? onImageInsertedCallback;
final OnImageInsertCallback? onImageInsertCallback;
}
typedef ImageEmbedBuilderWillRemoveCallback = Future<bool> Function(
String imageUrl,
);
typedef ImageEmbedBuilderOnRemovedCallback = Future<void> Function(
String imageUrl,
);
typedef ImageEmbedBuilderProviderBuilder = ImageProvider? Function(
BuildContext context,
String imageUrl,
);
typedef ImageEmbedBuilderErrorWidgetBuilder = ImageErrorWidgetBuilder;

View File

@ -1,36 +0,0 @@
import 'dart:async' show Completer;
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
class ImageLoader {
static ImageLoader _instance = ImageLoader();
static ImageLoader get instance => _instance;
/// Allows overriding the instance for testing
@visibleForTesting
static set instance(ImageLoader newInstance) => _instance = newInstance;
// TODO(performance): This will load the image again. In case
// this is a network image, then this will be inefficient.
Future<Uint8List?> loadImageBytesFromImageProvider({
required ImageProvider imageProvider,
}) async {
final stream = imageProvider.resolve(ImageConfiguration.empty);
final completer = Completer<ui.Image>();
ImageStreamListener? listener;
listener = ImageStreamListener((info, _) {
completer.complete(info.image);
stream.removeListener(listener!);
});
stream.addListener(listener);
final image = await completer.future;
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
return byteData?.buffer.asUint8List();
}
}

View File

@ -1,246 +0,0 @@
import 'package:flutter/cupertino.dart' show showCupertinoModalPopup;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart'
show ImageUrl, QuillController, StyleAttribute, getEmbedNode;
import 'package:flutter_quill/internal.dart';
import 'package:path/path.dart' as p;
import 'package:url_launcher/url_launcher.dart';
import '../../common/utils/element_utils/element_utils.dart';
import '../../common/utils/string.dart';
import 'config/image_config.dart';
import 'image_load_utils.dart';
import 'image_save_utils.dart';
import 'widgets/image.dart' show ImageTapWrapper, getImageStyleString;
import 'widgets/image_resizer.dart' show ImageResizer;
class ImageOptionsMenu extends StatelessWidget {
const ImageOptionsMenu({
required this.controller,
required this.config,
required this.imageSource,
required this.imageSize,
required this.readOnly,
required this.imageProvider,
this.prefersGallerySave = true,
super.key,
});
final QuillController controller;
final QuillEditorImageEmbedConfig config;
final String imageSource;
final ElementSize imageSize;
final bool readOnly;
final ImageProvider imageProvider;
// TODO(quill_native_bridge): Update this doc comment once saveImageToGallery()
// is supported on Windows too (will be applicable like macOS). See https://pub.dev/packages/quill_native_bridge#-features
/// Determines if the image should be saved to the gallery instead of using the
/// system file save dialog for platforms that support both.
///
/// Currently, the only platform where this applies is macOS.
///
/// This is silently ignored on platforms that only support gallery save (Android and iOS)
/// or only image save.
///
/// For more details, refer to [quill_native_bridge Saving images](https://pub.dev/packages/quill_native_bridge#-saving-images).
final bool prefersGallerySave;
@override
Widget build(BuildContext context) {
final materialTheme = Theme.of(context);
return Padding(
padding: const EdgeInsets.fromLTRB(50, 0, 50, 0),
child: SimpleDialog(
title: Text(context.loc.image),
children: [
if (!readOnly)
ListTile(
title: Text(context.loc.resize),
leading: const Icon(Icons.settings_outlined),
onTap: () {
Navigator.pop(context);
showCupertinoModalPopup<void>(
context: context,
builder: (modalContext) {
final screenSize = MediaQuery.sizeOf(modalContext);
return ImageResizer(
onImageResize: (width, height) {
final res = getEmbedNode(
controller,
controller.selection.start,
);
final attr = replaceStyleStringWithSize(
getImageStyleString(controller),
width: width,
height: height,
);
controller
..skipRequestKeyboard = true
..formatText(
res.offset,
1,
StyleAttribute(attr),
);
},
imageWidth: imageSize.width,
imageHeight: imageSize.height,
maxWidth: screenSize.width,
maxHeight: screenSize.height,
);
},
);
},
),
ListTile(
leading: const Icon(Icons.copy_all_outlined),
title: Text(context.loc.copy),
onTap: () async {
Navigator.of(context).pop();
controller.copiedImageUrl = ImageUrl(
imageSource,
getImageStyleString(controller),
);
final imageBytes = await ImageLoader.instance
.loadImageBytesFromImageProvider(
imageProvider: imageProvider);
if (imageBytes != null) {
await ClipboardServiceProvider.instance.copyImage(imageBytes);
}
},
),
if (!readOnly)
ListTile(
leading: Icon(
Icons.delete_forever_outlined,
color: materialTheme.colorScheme.error,
),
title: Text(context.loc.remove),
onTap: () async {
Navigator.of(context).pop();
// Call the remove check callback if set
if (await config.shouldRemoveImageCallback?.call(imageSource) ==
false) {
return;
}
final offset = getEmbedNode(
controller,
controller.selection.start,
).offset;
controller.replaceText(
offset,
1,
'',
TextSelection.collapsed(offset: offset),
);
// Call the post remove callback if set
await config.onImageRemovedCallback.call(imageSource);
},
),
ListTile(
leading: const Icon(Icons.save),
title: Text(context.loc.save),
onTap: () async {
final messenger = ScaffoldMessenger.of(context);
final localizations = context.loc;
Navigator.of(context).pop();
SaveImageResult? result;
try {
result = await ImageSaver.instance.saveImage(
imageUrl: imageSource,
imageProvider: imageProvider,
prefersGallerySave: prefersGallerySave,
);
} on GalleryImageSaveAccessDeniedException {
messenger.showSnackBar(SnackBar(
content: Text(
localizations.saveImagePermissionDenied,
)));
return;
}
if (result == null) {
messenger.showSnackBar(SnackBar(
content: Text(
localizations.errorUnexpectedSavingImage,
)));
return;
}
if (kIsWeb) {
messenger.showSnackBar(SnackBar(
content: Text(localizations.successImageDownloaded)));
return;
}
if (result.isGallerySave) {
messenger.showSnackBar(SnackBar(
content: Text(localizations.successImageSavedGallery),
action: SnackBarAction(
label: localizations.openGallery,
onPressed: () =>
QuillNativeProvider.instance.openGalleryApp(),
),
));
return;
}
if (isDesktopApp) {
final imageFilePath = result.imageFilePath;
if (imageFilePath == null) {
// User canceled the system save dialog.
return;
}
messenger.showSnackBar(
SnackBar(
content: Text(localizations.successImageSaved),
// On macOS the app only has access to the picked file from the system save
// dialog and not the directory where it was saved.
// Opening the directory of that file requires entitlements on macOS
// See https://pub.dev/packages/url_launcher#macos-file-access-configuration
// Open the saved image file instead of the directory
action: defaultTargetPlatform == TargetPlatform.macOS
? SnackBarAction(
label: localizations.openFile,
onPressed: () => launchUrl(Uri.file(imageFilePath)),
)
: SnackBarAction(
label: localizations.openFileLocation,
onPressed: () => launchUrl(
Uri.directory(p.dirname(imageFilePath))),
),
),
);
return;
}
throw StateError(
'Image save result is not handled on $defaultTargetPlatform');
},
),
ListTile(
leading: const Icon(Icons.zoom_in),
title: Text(context.loc.zoom),
onTap: () => Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => ImageTapWrapper(
imageUrl: imageSource,
config: config,
),
),
),
),
],
),
);
}
}

View File

@ -1,254 +0,0 @@
@internal
library;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_quill/internal.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'image_load_utils.dart';
const defaultImageFileExtension = 'png';
// The [imageSourcePath] could be file, asset path or HTTP image URL.
String extractImageFileExtensionFromImageSource(String? imageSourcePath) {
if (imageSourcePath == null || imageSourcePath.isEmpty) {
return defaultImageFileExtension;
}
if (!imageSourcePath.contains('.')) {
return defaultImageFileExtension;
}
return p.extension(imageSourcePath).replaceFirst('.', '');
}
// The [imageSourcePath] could be file, asset path or HTTP image URL.
String? extractImageNameFromImageSource(String? imageSourcePath) {
if (imageSourcePath == null || imageSourcePath.isEmpty) {
return null;
}
final uri = Uri.parse(imageSourcePath);
final pathWithoutQuery = uri.path;
final imageName = p.basenameWithoutExtension(pathWithoutQuery);
if (imageName.isEmpty) {
return null;
}
return imageName;
}
class SaveImageResult {
const SaveImageResult({
required this.imageFilePath,
required this.isGallerySave,
});
/// Returns `null` on web platforms, if [isGallerySave] is `true`
/// or in case the user cancels the save operation on desktop platforms.
final String? imageFilePath;
final bool isGallerySave;
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
if (other is! SaveImageResult) return false;
return other.imageFilePath == imageFilePath &&
other.isGallerySave == isGallerySave;
}
@override
int get hashCode => Object.hash(imageFilePath, isGallerySave);
@override
String toString() =>
'SaveImageResult(imageFilePath: $imageFilePath, isGallerySave: $isGallerySave)';
}
const String defaultImageFileNamePrefix = 'IMG';
String getDefaultImageFileName({required bool isGallerySave}) {
if (kIsWeb) {
// The browser handles name conflicts.
return defaultImageFileNamePrefix;
}
if (isGallerySave) {
// The gallery app handles name conflicts.
return defaultImageFileNamePrefix;
}
if (defaultTargetPlatform == TargetPlatform.macOS ||
defaultTargetPlatform == TargetPlatform.windows) {
// Windows and macOS system native save dialog prompts the user to confirm file overwrite.
return defaultImageFileNamePrefix;
}
final uniqueFileName =
'${defaultImageFileNamePrefix}_${DateTime.now().toIso8601String()}';
if (defaultTargetPlatform == TargetPlatform.linux) {
// IMPORTANT: On Linux, it depends on the desktop environment
// and name conflicts may not be handled. Always provide a unique image file name.
return uniqueFileName;
}
return uniqueFileName;
}
Future<bool> shouldSaveToGallery({required bool prefersGallerySave}) async {
final supportsGallerySave = await QuillNativeProvider.instance
.isSupported(QuillNativeBridgeFeature.saveImageToGallery);
if (!supportsGallerySave) {
return false;
}
final supportsImageSave = await QuillNativeProvider.instance
.isSupported(QuillNativeBridgeFeature.saveImage);
if (!supportsImageSave) {
return true;
}
return supportsGallerySave && prefersGallerySave;
}
/// Thrown when the gallery image save operation is denied
/// due to insufficient or denied permissions.
class GalleryImageSaveAccessDeniedException implements Exception {
GalleryImageSaveAccessDeniedException([this.message]);
final String? message;
@override
String toString() =>
message ??
'Permission to save the image to the gallery was denied or insufficient.';
}
class ImageSaver {
ImageSaver._();
static ImageSaver _instance = ImageSaver._();
static ImageSaver get instance => _instance;
/// Allows overriding the instance for testing
@visibleForTesting
static set instance(ImageSaver newInstance) => _instance = newInstance;
/// Saves an image to the user's device based on the platform:
///
/// - **Web**: Downloads the image using the browser's download functionality.
/// - **Desktop**: Prompts the user to choose a location for the image using
/// native save dialog, defaulting to the user's `Pictures` directory. Or
/// saves the image to the gallery in case [prefersGallerySave] is `true` and
// TODO(quill_native_bridge): Update this doc comment once saveImageToGallery()
// is supported on Windows too (will be applicable like macOS). See https://pub.dev/packages/quill_native_bridge#-features
/// the gallery is supported (currently only macOS is applicable).
/// - **Mobile**: Saves the image to the gallery, requesting permission if needed.
///
/// The [imageUrl] could be file or network image URL and is used to extract
/// image file extension and the image name.
///
/// The [imageProvider] is used to load the image bytes from using [ImageLoader].
///
/// Returns `null` on failure.
///
/// Throws [GalleryImageSaveAccessDeniedException] in case permission was denied or insuffeicnet.
Future<SaveImageResult?> saveImage({
required String imageUrl,
required ImageProvider imageProvider,
required bool prefersGallerySave,
}) async {
assert(() {
if (imageUrl.isEmpty) {
throw ArgumentError.value(imageUrl, 'imageUrl', 'cannot be empty');
}
return true;
}());
final imageFileExtension =
extractImageFileExtensionFromImageSource(imageUrl);
final imageName = extractImageNameFromImageSource(imageUrl);
final imageBytes = await ImageLoader.instance
.loadImageBytesFromImageProvider(imageProvider: imageProvider);
if (imageBytes == null || imageBytes.isEmpty) {
return null;
}
if (kIsWeb) {
await QuillNativeProvider.instance.saveImage(
imageBytes,
options: ImageSaveOptions(
name: imageName ?? getDefaultImageFileName(isGallerySave: false),
fileExtension: imageFileExtension),
);
return const SaveImageResult(
imageFilePath: null,
isGallerySave: false,
);
}
if (await shouldSaveToGallery(prefersGallerySave: prefersGallerySave)) {
try {
await QuillNativeProvider.instance.saveImageToGallery(
imageBytes,
options: GalleryImageSaveOptions(
name: imageName ?? getDefaultImageFileName(isGallerySave: true),
fileExtension: imageFileExtension,
// Specifying the album name requires read-write permission
// on iOS and macOS on all versions. Pass null to request add-only on
// supported versions (previous versions still use read-write).
albumName: null,
),
);
return const SaveImageResult(
imageFilePath: null,
isGallerySave: true,
);
} on PlatformException catch (e) {
// TODO(save-image): Part of https://github.com/FlutterQuill/quill-native-bridge/issues/2
// Permission request is required only on iOS, macOS and Android API 28 and earlier.
if (e.code == 'PERMISSION_DENIED') {
// macOS imposes security restrictions when running the app
// on sources other than Xcode or the macOS terminal, such as Android Studio or VS Code.
// This is not an issue in production. Throwing [GalleryImageSaveAccessDeniedException] will indicate
// that the user denied the permission, even though it will always deny the permission even if granted.
// Make sure we don't handle that error (it has details) during development to avoid confusion.
// For more details, see https://github.com/flutter/flutter/issues/134191#issuecomment-2506248266
// and https://pub.dev/packages/quill_native_bridge#-saving-images-to-the-gallery
final possiblePermissionIssueDuringDevelopmentOnMacOS =
kDebugMode && defaultTargetPlatform == TargetPlatform.macOS;
if (possiblePermissionIssueDuringDevelopmentOnMacOS) {
rethrow;
}
throw GalleryImageSaveAccessDeniedException(e.toString());
}
rethrow;
}
}
if (await QuillNativeProvider.instance
.isSupported(QuillNativeBridgeFeature.saveImage)) {
assert(!isMobileApp,
'Mobile platforms support saving images to the gallery only');
final result = await QuillNativeProvider.instance.saveImage(
imageBytes,
options: ImageSaveOptions(
name: imageName ?? getDefaultImageFileName(isGallerySave: false),
fileExtension: imageFileExtension,
),
);
return SaveImageResult(
imageFilePath: result.filePath,
isGallerySave: false,
);
}
throw StateError('Image save is not handled on $defaultTargetPlatform');
}
}

View File

@ -1,64 +1 @@
import 'package:flutter/foundation.dart' show kIsWeb; // TODO Implement this library.
import 'package:flutter/widgets.dart';
import 'package:flutter_quill/flutter_quill.dart';
import '../../common/utils/element_utils/element_web_utils.dart';
import '../../common/utils/utils.dart';
import '../../common/utils/web/web.dart';
import 'config/image_web_config.dart';
class QuillEditorWebImageEmbedBuilder extends EmbedBuilder {
const QuillEditorWebImageEmbedBuilder({
required this.config,
});
final QuillEditorWebImageEmbedConfig config;
@override
String get key => BlockEmbed.imageType;
@override
bool get expanded => false;
@override
Widget build(
BuildContext context,
EmbedContext embedContext,
) {
assert(kIsWeb, 'ImageEmbedBuilderWeb is only for web platform');
final (height, width, margin, alignment) =
getWebElementAttributes(embedContext.node);
var imageSource = embedContext.node.value.data.toString();
// This logic make sure if the image is imageBase64 then
// it make sure if the pattern is like
// data:image/png;base64, [base64 encoded image string here]
// if not then it will add the data:image/png;base64, at the first
if (isImageBase64(imageSource)) {
// Sometimes the image base 64 for some reasons
// doesn't displayed with the 'data:image/png;base64'
if (!(imageSource.startsWith('data:image/') &&
imageSource.contains('base64'))) {
imageSource = 'data:image/png;base64, $imageSource';
}
}
createHtmlImageElement(
src: imageSource,
alignSelf: alignment,
width: width,
height: height,
margin: margin,
);
return ConstrainedBox(
constraints:
config.constraints ?? BoxConstraints.loose(const Size(200, 200)),
child: HtmlElementView(
viewType: imageSource,
),
);
}
}

View File

@ -1,186 +0,0 @@
import 'dart:convert' show base64;
import 'dart:io' show File;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:photo_view/photo_view.dart';
import '../../../common/utils/utils.dart';
import '../config/image_config.dart';
import '../image_embed_types.dart';
String getImageStyleString(QuillController controller) {
final String? s = controller
.getAllSelectionStyles()
.firstWhere((s) => s.attributes.containsKey(Attribute.style.key),
orElse: Style.new)
.attributes[Attribute.style.key]
?.value;
return s ?? '';
}
/// [imageProviderBuilder] To override the return value pass value to it
/// [imageSource] The source of the image in the quill delta json document
/// It could be http, file, network, asset, or base 64 image
ImageProvider getImageProviderByImageSource(
String imageSource, {
required ImageEmbedBuilderProviderBuilder? imageProviderBuilder,
required BuildContext context,
}) {
if (imageProviderBuilder != null) {
final imageProvider = imageProviderBuilder(context, imageSource);
if (imageProvider != null) {
return imageProvider;
}
}
if (isImageBase64(imageSource)) {
return MemoryImage(base64.decode(imageSource));
}
if (isHttpUrl(imageSource)) {
return NetworkImage(imageSource);
}
// File image
if (kIsWeb) {
return NetworkImage(imageSource);
}
return FileImage(File(imageSource));
}
Image getImageWidgetByImageSource(
String imageSource, {
required BuildContext context,
required ImageEmbedBuilderProviderBuilder? imageProviderBuilder,
required ImageErrorWidgetBuilder? imageErrorWidgetBuilder,
double? width,
double? height,
AlignmentGeometry alignment = Alignment.center,
}) {
return Image(
image: getImageProviderByImageSource(
context: context,
imageSource,
imageProviderBuilder: imageProviderBuilder,
),
width: width,
height: height,
alignment: alignment,
errorBuilder: imageErrorWidgetBuilder,
);
}
String standardizeImageUrl(String url) {
if (url.contains('base64')) {
return url.split(',')[1];
}
return url;
}
const List<String> _imageFileExtensions = [
'.jpeg',
'.png',
'.jpg',
'.gif',
'.webp',
'.tif',
'.heic'
];
/// This is a bug of Gallery Saver Package.
/// It can not save image that's filename does not end with it's file extension
/// like below.
// "https://firebasestorage.googleapis.com/v0/b/eventat-4ba96.appspot.com/o/2019-Metrology-Events.jpg?alt=media&token=bfc47032-5173-4b3f-86bb-9659f46b362a"
/// If imageUrl does not end with it's file extension,
/// file extension is added to image url for saving.
String appendFileExtensionToImageUrl(String url) {
final endsWithImageFileExtension = _imageFileExtensions
.firstWhere((s) => url.toLowerCase().endsWith(s), orElse: () => '');
if (endsWithImageFileExtension.isNotEmpty) {
return url;
}
final imageFileExtension = _imageFileExtensions
.firstWhere((s) => url.toLowerCase().contains(s), orElse: () => '');
return url + imageFileExtension;
}
class ImageTapWrapper extends StatelessWidget {
const ImageTapWrapper({
required this.imageUrl,
required this.config,
super.key,
});
final String imageUrl;
final QuillEditorImageEmbedConfig config;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
constraints: BoxConstraints.expand(
height: MediaQuery.sizeOf(context).height,
),
child: Stack(
children: [
PhotoView(
imageProvider: getImageProviderByImageSource(
context: context,
imageUrl,
imageProviderBuilder: config.imageProviderBuilder,
),
errorBuilder: config.imageErrorWidgetBuilder,
loadingBuilder: (context, event) {
return Container(
color: Colors.black,
child: const Center(
child: CircularProgressIndicator(),
),
);
},
),
Positioned(
right: 10,
top: MediaQuery.paddingOf(context).top + 10.0,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Stack(
children: [
Opacity(
opacity: 0.2,
child: Container(
height: 30,
width: 30,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.black87,
),
),
),
Positioned(
top: 0,
bottom: 0,
left: 0,
right: 0,
child: Icon(
Icons.close,
color: Colors.grey[400],
size: 28,
),
)
],
),
),
),
],
),
),
);
}
}

View File

@ -1,126 +0,0 @@
import 'package:flutter/cupertino.dart'
show CupertinoActionSheet, CupertinoActionSheetAction;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show SchedulerBinding;
import 'package:flutter_quill/internal.dart';
class ImageResizer extends StatefulWidget {
const ImageResizer({
required this.imageWidth,
required this.imageHeight,
required this.maxWidth,
required this.maxHeight,
required this.onImageResize,
super.key,
});
final double? imageWidth;
final double? imageHeight;
final double maxWidth;
final double maxHeight;
final Function(double width, double height) onImageResize;
@override
ImageResizerState createState() => ImageResizerState();
}
class ImageResizerState extends State<ImageResizer> {
late double _width;
late double _height;
@override
void initState() {
super.initState();
_width = widget.imageWidth ?? widget.maxWidth;
_height = widget.imageHeight ?? widget.maxHeight;
}
@override
Widget build(BuildContext context) {
if (Theme.of(context).isCupertino) {
return _showCupertinoMenu();
}
return _showMaterialMenu();
}
Widget _showMaterialMenu() {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
_widthSlider(),
_heightSlider(),
],
);
}
Widget _showCupertinoMenu() {
return CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () {},
child: _widthSlider(),
),
CupertinoActionSheetAction(
onPressed: () {},
child: _heightSlider(),
)
],
);
}
Widget _slider({
required bool isWidth,
required ValueChanged<double> onChanged,
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Card(
child: Slider.adaptive(
value: isWidth ? _width : _height,
max: isWidth ? widget.maxWidth : widget.maxHeight,
divisions: 1000,
// Might need to be changed
label: isWidth ? context.loc.width : context.loc.height,
onChanged: (val) {
setState(() {
onChanged(val);
_resizeImage();
});
},
),
),
);
}
Widget _heightSlider() {
return _slider(
isWidth: false,
onChanged: (value) {
_height = value;
},
);
}
Widget _widthSlider() {
return _slider(
isWidth: true,
onChanged: (value) {
_width = value;
},
);
}
bool _scheduled = false;
void _resizeImage() {
if (_scheduled) {
return;
}
_scheduled = true;
SchedulerBinding.instance.addPostFrameCallback((_) {
widget.onImageResize(_width, _height);
_scheduled = false;
});
}
}

View File

@ -1,46 +1 @@
import 'package:flutter/widgets.dart' show GlobalKey, Widget; // TODO Implement this library.
import 'package:meta/meta.dart' show experimental, immutable;
@immutable
class QuillEditorVideoEmbedConfig {
const QuillEditorVideoEmbedConfig({
this.onVideoInit,
this.customVideoBuilder,
});
/// [onVideoInit] is a callback function that gets triggered when
/// a video is initialized.
/// You can use this to perform actions or setup configurations related
/// to video embedding.
///
///
/// Example usage:
/// ```dart
/// onVideoInit: (videoContainerKey) {
/// // Custom video initialization logic
/// },
/// // Customize other callback functions as needed
/// ```
final void Function(GlobalKey videoContainerKey)? onVideoInit;
/// [customVideoBuilder] is a callback function that receives the
/// video URL and a read-only flag. This allows users to define
/// their own logic for rendering video widgets, enabling support
/// for various video platforms, such as YouTube.
///
/// Example usage:
/// ```dart
/// customVideoBuilder: (videoUrl, readOnly) {
/// // Return `null` to fallback to defualt logic of QuillEditorVideoEmbedBuilder
///
/// // Return a custom video widget based on the videoUrl
/// return CustomVideoWidget(videoUrl: videoUrl, readOnly: readOnly);
/// },
/// ```
///
/// It's a quick solution as response to https://github.com/singerdmx/flutter-quill/issues/2284
///
/// **Might be removed or changed in future releases.**
@experimental
final Widget? Function(String videoUrl, bool readOnly)? customVideoBuilder;
}

View File

@ -1,6 +1 @@
import 'package:meta/meta.dart' show immutable; // TODO Implement this library.
@immutable
class QuillEditorWebVideoEmbedConfig {
const QuillEditorWebVideoEmbedConfig();
}

View File

@ -1,55 +1 @@
import 'package:flutter/material.dart'; // TODO Implement this library.
import 'package:flutter_quill/flutter_quill.dart';
import '../../common/utils/element_utils/element_utils.dart';
import 'config/video_config.dart';
import 'widgets/video_app.dart';
class QuillEditorVideoEmbedBuilder extends EmbedBuilder {
const QuillEditorVideoEmbedBuilder({
required this.config,
});
final QuillEditorVideoEmbedConfig config;
@override
String get key => BlockEmbed.videoType;
@override
bool get expanded => false;
@override
Widget build(
BuildContext context,
EmbedContext embedContext,
) {
final videoUrl = embedContext.node.value.data;
final customVideoBuilder = config.customVideoBuilder;
if (customVideoBuilder != null) {
final videoWidget = customVideoBuilder(videoUrl, embedContext.readOnly);
if (videoWidget != null) {
return videoWidget;
}
}
final ((elementSize), margin, alignment) = getElementAttributes(
embedContext.node,
context,
);
final width = elementSize.width;
final height = elementSize.height;
return Container(
width: width,
height: height,
margin: EdgeInsets.all(margin ?? 0.0),
alignment: alignment,
child: VideoApp(
videoUrl: videoUrl,
readOnly: embedContext.readOnly,
onVideoInit: config.onVideoInit,
),
);
}
}

View File

@ -1,55 +1 @@
import 'package:flutter/widgets.dart'; // TODO Implement this library.
import 'package:flutter_quill/flutter_quill.dart';
import '../../common/utils/element_utils/element_web_utils.dart';
import '../../common/utils/utils.dart';
import '../../common/utils/web/web.dart';
import 'config/video_web_config.dart';
import 'youtube_video_url.dart';
class QuillEditorWebVideoEmbedBuilder extends EmbedBuilder {
const QuillEditorWebVideoEmbedBuilder({
required this.config,
});
final QuillEditorWebVideoEmbedConfig config;
@override
String get key => BlockEmbed.videoType;
@override
bool get expanded => false;
@override
Widget build(
BuildContext context,
EmbedContext embedContext,
) {
var videoUrl = embedContext.node.value.data;
if (isYouTubeUrl(videoUrl)) {
// ignore: deprecated_member_use_from_same_package
final youtubeID = convertVideoUrlToId(videoUrl);
if (youtubeID != null) {
videoUrl = 'https://www.youtube.com/embed/$youtubeID';
}
}
final (height, width, margin, alignment) =
getWebElementAttributes(embedContext.node);
createHtmlIFrameElement(
src: videoUrl,
width: width,
height: height,
margin: margin,
alignSelf: alignment,
);
return SizedBox(
height: 500,
child: HtmlElementView(
viewType: videoUrl,
),
);
}
}

View File

@ -1,122 +0,0 @@
import 'dart:io' show File;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:video_player/video_player.dart';
import '../../../common/utils/utils.dart';
/// Widget for playing back video
/// Refer to https://github.com/flutter/plugins/tree/master/packages/video_player/video_player
class VideoApp extends StatefulWidget {
const VideoApp({
required this.videoUrl,
required this.readOnly,
super.key,
this.onVideoInit,
});
final String videoUrl;
final bool readOnly;
final void Function(GlobalKey videoContainerKey)? onVideoInit;
@override
VideoAppState createState() => VideoAppState();
}
class VideoAppState extends State<VideoApp> {
late VideoPlayerController _controller;
GlobalKey videoContainerKey = GlobalKey();
@override
void initState() {
super.initState();
_controller = isHttpUrl(widget.videoUrl)
? VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl))
: VideoPlayerController.file(File(widget.videoUrl))
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized,
// even before the play button has been pressed.
setState(() {});
if (widget.onVideoInit != null) {
widget.onVideoInit?.call(videoContainerKey);
}
}).catchError((error) {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
final defaultStyles = DefaultStyles.getInstance(context);
if (_controller.value.hasError) {
if (widget.readOnly) {
return RichText(
text: TextSpan(
text: widget.videoUrl,
style: defaultStyles.link,
recognizer: TapGestureRecognizer()
..onTap = () => launchUrl(
Uri.parse(widget.videoUrl),
),
),
);
}
return RichText(
text: TextSpan(
text: widget.videoUrl,
style: defaultStyles.link,
),
);
} else if (!_controller.value.isInitialized) {
return VideoProgressIndicator(
_controller,
allowScrubbing: true,
colors: const VideoProgressColors(playedColor: Colors.blue),
);
}
return Container(
key: videoContainerKey,
child: InkWell(
onTap: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
child: Stack(
alignment: Alignment.center,
children: [
Center(
child: AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)),
_controller.value.isPlaying
? const SizedBox.shrink()
: Container(
color: const Color(0xfff5f5f5),
child: const Icon(
Icons.play_arrow,
size: 60,
color: Colors.blueGrey,
),
)
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -1,32 +0,0 @@
import 'package:meta/meta.dart';
/// Function copied from https://github.com/sarbagyastha/youtube_player_flutter/blob/f8e1e79991066bcc70f0a7c93941ca0d54b7370e/packages/youtube_player_flutter/lib/src/player/youtube_player.dart#L154
/// and is not written as part of this project.
///
/// Used as quick response for https://github.com/singerdmx/flutter-quill/issues/2284
@experimental
@internal
@Deprecated(
'Will be removed in future releases, for now included as quick response to https://github.com/singerdmx/flutter-quill/issues/2284',
)
String? convertVideoUrlToId(String url, {bool trimWhitespaces = true}) {
if (!url.contains('http') && (url.length == 11)) return url;
if (trimWhitespaces) url = url.trim();
for (final exp in [
RegExp(
r'^https:\/\/(?:www\.|m\.)?youtube\.com\/watch\?v=([_\-a-zA-Z0-9]{11}).*$'),
RegExp(
r'^https:\/\/(?:music\.)?youtube\.com\/watch\?v=([_\-a-zA-Z0-9]{11}).*$'),
RegExp(
r'^https:\/\/(?:www\.|m\.)?youtube\.com\/shorts\/([_\-a-zA-Z0-9]{11}).*$'),
RegExp(
r'^https:\/\/(?:www\.|m\.)?youtube(?:-nocookie)?\.com\/embed\/([_\-a-zA-Z0-9]{11}).*$'),
RegExp(r'^https:\/\/youtu\.be\/([_\-a-zA-Z0-9]{11}).*$')
]) {
final Match? match = exp.firstMatch(url);
if (match != null && match.groupCount >= 1) return match.group(1);
}
return null;
}

View File

@ -1,106 +1 @@
import 'package:flutter/foundation.dart' show kIsWeb; // TODO Implement this library.
import 'package:flutter_quill/flutter_quill.dart';
import 'editor/image/config/image_config.dart';
import 'editor/image/image_embed.dart';
import 'editor/video/config/video_config.dart';
import 'editor/video/config/video_web_config.dart';
import 'editor/video/video_embed.dart';
import 'editor/video/video_web_embed.dart';
import 'toolbar/camera/camera_button.dart';
import 'toolbar/camera/config/camera_config.dart';
import 'toolbar/image/config/image_config.dart';
import 'toolbar/image/image_button.dart';
import 'toolbar/video/config/video_config.dart';
import 'toolbar/video/video_button.dart';
abstract final class FlutterQuillEmbeds {
/// Returns a list of embed builders for [QuillEditor]
/// to provide basic support for loading images and videos.
///
static List<EmbedBuilder> editorBuilders({
QuillEditorImageEmbedConfig? imageEmbedConfig =
const QuillEditorImageEmbedConfig(),
QuillEditorVideoEmbedConfig? videoEmbedConfig =
const QuillEditorVideoEmbedConfig(),
}) {
return [
if (imageEmbedConfig != null)
QuillEditorImageEmbedBuilder(
config: imageEmbedConfig,
),
if (videoEmbedConfig != null)
QuillEditorVideoEmbedBuilder(
config: videoEmbedConfig,
),
];
}
/// Returns a list of embed builders specifically designed for web support
/// to load images and videos.
///
static List<EmbedBuilder> editorWebBuilders({
QuillEditorImageEmbedConfig? imageEmbedConfig =
const QuillEditorImageEmbedConfig(),
QuillEditorWebVideoEmbedConfig? videoEmbedConfig =
const QuillEditorWebVideoEmbedConfig(),
}) {
if (!kIsWeb) {
throw UnsupportedError(
'The ${FlutterQuillEmbeds.editorWebBuilders} is for web, use ${FlutterQuillEmbeds.editorBuilders} '
'instead for non-web platforms',
);
}
return [
if (imageEmbedConfig != null)
QuillEditorImageEmbedBuilder(
config: imageEmbedConfig,
),
if (videoEmbedConfig != null)
QuillEditorWebVideoEmbedBuilder(
config: videoEmbedConfig,
),
];
}
/// Returns a list of embed builders for [QuillEditor].
///
/// It will use [editorWebBuilders] for web and [editorBuilders] for non-web platforms.
static List<EmbedBuilder> defaultEditorBuilders() {
return kIsWeb ? editorWebBuilders() : editorBuilders();
}
/// Returns a list of embed button builders to support images and videos.
///
/// Pass `null` to options of a button to not show it.
static List<EmbedButtonBuilder> toolbarButtons({
QuillToolbarImageButtonOptions? imageButtonOptions =
const QuillToolbarImageButtonOptions(),
QuillToolbarVideoButtonOptions? videoButtonOptions =
const QuillToolbarVideoButtonOptions(),
QuillToolbarCameraButtonOptions? cameraButtonOptions,
}) =>
[
if (imageButtonOptions != null)
(context, embedContext) => QuillToolbarImageButton(
controller: embedContext.controller,
options: imageButtonOptions,
// ignore: invalid_use_of_internal_member
baseOptions: embedContext.baseButtonOptions,
),
if (videoButtonOptions != null)
(context, embedContext) => QuillToolbarVideoButton(
controller: embedContext.controller,
options: videoButtonOptions,
// ignore: invalid_use_of_internal_member
baseOptions: embedContext.baseButtonOptions,
),
if (cameraButtonOptions != null)
(context, embedContext) => QuillToolbarCameraButton(
controller: embedContext.controller,
options: cameraButtonOptions,
// ignore: invalid_use_of_internal_member
baseOptions: embedContext.baseButtonOptions,
),
];
}

View File

@ -1,132 +1 @@
import 'package:flutter/material.dart'; // TODO Implement this library.
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/internal.dart';
import 'package:image_picker/image_picker.dart';
import '../../common/default_image_insert.dart';
import '../../common/default_video_insert.dart';
import '../quill_simple_toolbar_api.dart';
import 'camera_types.dart';
import 'config/camera_config.dart';
import 'select_camera_action.dart';
// ignore: invalid_use_of_internal_member
class QuillToolbarCameraButton extends QuillToolbarBaseButtonStateless {
const QuillToolbarCameraButton({
required super.controller,
QuillToolbarCameraButtonOptions? options,
/// Shares common options between all buttons, prefer the [options]
/// over the [baseOptions].
super.baseOptions,
super.key,
}) : _options = options,
super(options: options);
final QuillToolbarCameraButtonOptions? _options;
@override
QuillToolbarCameraButtonOptions? get options => _options;
void _sharedOnPressed(BuildContext context) {
_onPressedHandler(
context,
controller,
);
afterButtonPressed(context);
}
Future<CameraAction?> _getCameraAction(BuildContext context) async {
final customCallback = options?.cameraConfig?.onRequestCameraActionCallback;
if (customCallback != null) {
return await customCallback(context);
}
final cameraAction = await showSelectCameraActionDialog(
context: context,
);
return cameraAction;
}
Future<void> _onPressedHandler(
BuildContext context,
QuillController controller,
) async {
final cameraAction = await _getCameraAction(context);
if (cameraAction == null) {
return;
}
switch (cameraAction) {
case CameraAction.video:
final videoFile =
await ImagePicker().pickVideo(source: ImageSource.camera);
if (videoFile == null) {
return;
}
await handleVideoInsert(
videoFile.path,
controller: controller,
onVideoInsertCallback: options?.cameraConfig?.onVideoInsertCallback,
onVideoInsertedCallback:
options?.cameraConfig?.onVideoInsertedCallback,
);
case CameraAction.image:
final imageFile =
await ImagePicker().pickImage(source: ImageSource.camera);
if (imageFile == null) {
return;
}
await handleImageInsert(
imageFile.path,
controller: controller,
onImageInsertCallback: options?.cameraConfig?.onImageInsertCallback,
onImageInsertedCallback:
options?.cameraConfig?.onImageInsertedCallback,
);
}
}
@override
Widget buildButton(BuildContext context) {
return QuillToolbarIconButton(
icon: Icon(
iconData(context),
size: iconButtonFactor(context) * iconSize(context),
),
tooltip: tooltip(context),
isSelected: false,
onPressed: () => _sharedOnPressed(context),
iconTheme: iconTheme(context),
);
}
@override
Widget? buildCustomChildBuilder(BuildContext context) {
return childBuilder?.call(
QuillToolbarCameraButtonOptions(
afterButtonPressed: afterButtonPressed(context),
iconData: iconData(context),
iconSize: iconSize(context),
iconButtonFactor: iconButtonFactor(context),
iconTheme: options?.iconTheme,
tooltip: tooltip(context),
cameraConfig: options?.cameraConfig,
),
QuillToolbarCameraButtonExtraOptions(
controller: controller,
context: context,
onPressed: () => _sharedOnPressed(context),
),
);
}
@override
IconData Function(BuildContext context) get getDefaultIconData =>
(context) => Icons.photo_camera;
@override
String Function(BuildContext context) get getDefaultTooltip =>
(context) => context.loc.camera;
}

View File

@ -1,39 +1 @@
import 'package:flutter/widgets.dart' show BuildContext; // TODO Implement this library.
import 'package:meta/meta.dart' show immutable;
import '../../editor/image/image_embed_types.dart';
import '../video/config/video.dart';
enum CameraAction {
video,
image,
}
/// When the user click the camera button, should we take a photo or record
/// a video using the camera
///
/// by default will show a dialog that ask the user which option he/she wants
typedef OnRequestCameraActionCallback = Future<CameraAction?> Function(
BuildContext context,
);
@immutable
class QuillToolbarCameraConfig {
const QuillToolbarCameraConfig({
this.onRequestCameraActionCallback,
this.onImageInsertCallback,
this.onImageInsertedCallback,
this.onVideoInsertedCallback,
this.onVideoInsertCallback,
});
final OnRequestCameraActionCallback? onRequestCameraActionCallback;
final OnImageInsertedCallback? onImageInsertedCallback;
final OnImageInsertCallback? onImageInsertCallback;
final OnVideoInsertedCallback? onVideoInsertedCallback;
final OnVideoInsertCallback? onVideoInsertCallback;
}

View File

@ -1,28 +1 @@
import 'package:flutter_quill/flutter_quill.dart'; // TODO Implement this library.
import '../camera_types.dart';
class QuillToolbarCameraButtonExtraOptions
extends QuillToolbarBaseButtonExtraOptions {
const QuillToolbarCameraButtonExtraOptions({
required super.controller,
required super.context,
required super.onPressed,
});
}
class QuillToolbarCameraButtonOptions extends QuillToolbarBaseButtonOptions<
QuillToolbarCameraButtonOptions, QuillToolbarCameraButtonExtraOptions> {
const QuillToolbarCameraButtonOptions({
this.cameraConfig,
super.iconSize,
super.iconButtonFactor,
super.iconData,
super.afterButtonPressed,
super.tooltip,
super.iconTheme,
super.childBuilder,
});
final QuillToolbarCameraConfig? cameraConfig;
}

View File

@ -1,52 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/internal.dart';
import 'camera_types.dart';
class SelectCameraActionDialog extends StatelessWidget {
const SelectCameraActionDialog({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 150,
width: double.infinity,
child: SingleChildScrollView(
child: Column(
children: [
ListTile(
title: Text(context.loc.photo),
subtitle: Text(
context.loc.takeAPhotoUsingYourCamera,
),
leading: const Icon(Icons.photo_sharp),
enabled: !isDesktopApp,
onTap: () => Navigator.of(context).pop(CameraAction.image),
),
ListTile(
title: Text(context.loc.video),
subtitle: Text(
context.loc.recordAVideoUsingYourCamera,
),
leading: const Icon(Icons.camera),
enabled: !isDesktopApp,
onTap: () => Navigator.of(context).pop(CameraAction.video),
),
],
),
),
);
}
}
Future<CameraAction?> showSelectCameraActionDialog({
required BuildContext context,
}) async {
final imageSource = await showModalBottomSheet<CameraAction>(
showDragHandle: true,
context: context,
constraints: const BoxConstraints(maxWidth: 640),
builder: (context) => const SelectCameraActionDialog(),
);
return imageSource;
}

View File

@ -1,39 +1 @@
import 'package:flutter_quill/flutter_quill.dart'; // TODO Implement this library.
import 'package:meta/meta.dart' show immutable;
import '../../../editor/image/image_embed_types.dart';
class QuillToolbarImageButtonExtraOptions
extends QuillToolbarBaseButtonExtraOptions {
const QuillToolbarImageButtonExtraOptions({
required super.controller,
required super.context,
required super.onPressed,
});
}
@immutable
class QuillToolbarImageButtonOptions extends QuillToolbarBaseButtonOptions<
QuillToolbarImageButtonOptions, QuillToolbarImageButtonExtraOptions> {
const QuillToolbarImageButtonOptions({
super.iconData,
super.iconSize,
super.iconButtonFactor,
/// specifies the tooltip text for the image button.
super.tooltip,
super.afterButtonPressed,
super.childBuilder,
super.iconTheme,
this.dialogTheme,
this.linkRegExp,
this.imageButtonConfig = const QuillToolbarImageConfig(),
});
final QuillDialogTheme? dialogTheme;
/// [imageLinkRegExp] is a regular expression to identify image links.
final RegExp? linkRegExp;
final QuillToolbarImageConfig? imageButtonConfig;
}

View File

@ -1,135 +1 @@
import 'package:flutter/material.dart'; // TODO Implement this library.
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/internal.dart';
import 'package:image_picker/image_picker.dart';
import '../../common/default_image_insert.dart';
import '../../common/image_video_utils.dart';
import '../../editor/image/image_embed_types.dart';
import '../quill_simple_toolbar_api.dart';
import 'config/image_config.dart';
import 'select_image_source.dart';
// ignore: invalid_use_of_internal_member
class QuillToolbarImageButton extends QuillToolbarBaseButtonStateless {
const QuillToolbarImageButton({
required super.controller,
QuillToolbarImageButtonOptions? options,
/// Shares common options between all buttons, prefer the [options]
/// over the [baseOptions].
super.baseOptions,
super.key,
}) : _options = options,
super(options: options);
final QuillToolbarImageButtonOptions? _options;
@override
QuillToolbarImageButtonOptions? get options => _options;
void _sharedOnPressed(BuildContext context) {
_onPressedHandler(context);
afterButtonPressed(context);
}
Future<void> _handleImageInsert(String imageUrl) async {
await handleImageInsert(
imageUrl,
controller: controller,
onImageInsertCallback: options?.imageButtonConfig?.onImageInsertCallback,
onImageInsertedCallback:
options?.imageButtonConfig?.onImageInsertedCallback,
);
}
Future<void> _onPressedHandler(BuildContext context) async {
final onRequestPickImage = options?.imageButtonConfig?.onRequestPickImage;
if (onRequestPickImage != null) {
final imageUrl = await onRequestPickImage(
context,
);
if (imageUrl != null) {
await _handleImageInsert(imageUrl);
}
return;
}
final source = await showSelectImageSourceDialog(
context: context,
);
if (source == null) {
return;
}
final imageUrl = switch (source) {
InsertImageSource.gallery =>
(await ImagePicker().pickImage(source: ImageSource.gallery))?.path,
InsertImageSource.link =>
context.mounted ? await _typeLink(context) : null,
InsertImageSource.camera =>
(await ImagePicker().pickImage(source: ImageSource.camera))?.path,
};
if (imageUrl == null) {
return;
}
if (imageUrl.trim().isNotEmpty) {
await _handleImageInsert(imageUrl);
}
}
Future<String?> _typeLink(BuildContext context) async {
final value = await showDialog<String>(
context: context,
builder: (_) => TypeLinkDialog(
dialogTheme: options?.dialogTheme,
linkRegExp: options?.linkRegExp,
linkType: LinkType.image,
),
);
return value;
}
@override
Widget buildButton(BuildContext context) {
return QuillToolbarIconButton(
icon: Icon(
iconData(context),
size: iconButtonFactor(context) * iconSize(context),
),
tooltip: tooltip(context),
isSelected: false,
onPressed: () => _sharedOnPressed(context),
iconTheme: iconTheme(context),
);
}
@override
Widget? buildCustomChildBuilder(BuildContext context) {
return childBuilder?.call(
QuillToolbarImageButtonOptions(
afterButtonPressed: afterButtonPressed(context),
iconData: iconData(context),
iconSize: iconSize(context),
iconButtonFactor: iconButtonFactor(context),
dialogTheme: options?.dialogTheme,
iconTheme: options?.iconTheme,
linkRegExp: options?.linkRegExp,
tooltip: tooltip(context),
imageButtonConfig: options?.imageButtonConfig,
),
QuillToolbarImageButtonExtraOptions(
context: context,
controller: controller,
onPressed: () => _sharedOnPressed(context),
),
);
}
@override
IconData Function(BuildContext context) get getDefaultIconData =>
(context) => Icons.image;
@override
String Function(BuildContext context) get getDefaultTooltip =>
(context) => context.loc.insertImage;
}

View File

@ -1,59 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/internal.dart';
import '../../editor/image/image_embed_types.dart';
class SelectImageSourceDialog extends StatelessWidget {
const SelectImageSourceDialog({super.key});
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(minHeight: 200),
width: double.infinity,
child: SingleChildScrollView(
child: Column(
children: [
ListTile(
title: Text(context.loc.gallery),
subtitle: Text(
context.loc.pickAPhotoFromYourGallery,
),
leading: const Icon(Icons.photo_sharp),
onTap: () => Navigator.of(context).pop(InsertImageSource.gallery),
),
ListTile(
title: Text(context.loc.camera),
subtitle: Text(
context.loc.takeAPhotoUsingYourCamera,
),
leading: const Icon(Icons.camera),
enabled: !isDesktopApp,
onTap: () => Navigator.of(context).pop(InsertImageSource.camera),
),
ListTile(
title: Text(context.loc.link),
subtitle: Text(
context.loc.pasteAPhotoUsingALink,
),
leading: const Icon(Icons.link),
onTap: () => Navigator.of(context).pop(InsertImageSource.link),
),
],
),
),
);
}
}
Future<InsertImageSource?> showSelectImageSourceDialog({
required BuildContext context,
}) async {
final imageSource = await showModalBottomSheet<InsertImageSource>(
showDragHandle: true,
context: context,
constraints: const BoxConstraints(maxWidth: 640),
builder: (_) => const SelectImageSourceDialog(),
);
return imageSource;
}

View File

@ -1,12 +0,0 @@
/// APIs that are meant to be used by the `flutter_quil_extensions` only.
///
/// Breaking changes can be introduced from `flutter_quill` in minor versions,
/// the `flutter_quill_extensions` will be updated and published at the same time.
///
/// Update both packages and use the same version for compatibility by running `flutter pub upgrade`.
@internal
library;
import 'package:meta/meta.dart';
export 'package:flutter_quill/src/toolbar/base_button/stateless_base_button.dart';

View File

@ -1,50 +1 @@
import 'package:flutter/widgets.dart' show BuildContext; // TODO Implement this library.
import 'package:flutter_quill/flutter_quill.dart';
import 'package:meta/meta.dart' show immutable;
/// When request picking an video, for example when the video button toolbar
/// clicked, it should be null in case the user didn't choose any video or
/// any other reasons, and it should be the video file path as string that is
/// exists in case the user picked the video successfully
///
/// by default we already have a default implementation that show a dialog
/// request the source for picking the video, from gallery, link or camera
typedef OnRequestPickVideo = Future<String?> Function(
BuildContext context,
);
/// A callback will called when inserting a video in the editor
/// it have the logic that will insert the video block using the controller
typedef OnVideoInsertCallback = Future<void> Function(
String video,
QuillController controller,
);
/// When a new video picked this callback will called and you might want to
/// do some logic depending on your use case
typedef OnVideoInsertedCallback = Future<void> Function(
String video,
);
enum InsertVideoSource {
gallery,
camera,
link,
}
/// Configurations for dealing with videos, on insert a video
/// on request picking a video
@immutable
class QuillToolbarVideoConfig {
const QuillToolbarVideoConfig({
this.onRequestPickVideo,
this.onVideoInsertedCallback,
this.onVideoInsertCallback,
});
final OnRequestPickVideo? onRequestPickVideo;
final OnVideoInsertedCallback? onVideoInsertedCallback;
final OnVideoInsertCallback? onVideoInsertCallback;
}

View File

@ -1,32 +1 @@
import 'package:flutter_quill/flutter_quill.dart'; // TODO Implement this library.
import 'video.dart';
class QuillToolbarVideoButtonExtraOptions
extends QuillToolbarBaseButtonExtraOptions {
const QuillToolbarVideoButtonExtraOptions({
required super.controller,
required super.context,
required super.onPressed,
});
}
class QuillToolbarVideoButtonOptions extends QuillToolbarBaseButtonOptions<
QuillToolbarVideoButtonOptions, QuillToolbarVideoButtonExtraOptions> {
const QuillToolbarVideoButtonOptions({
this.linkRegExp,
this.dialogTheme,
super.iconSize,
super.iconButtonFactor,
super.iconData,
super.afterButtonPressed,
super.tooltip,
super.iconTheme,
super.childBuilder,
this.videoConfig,
});
final RegExp? linkRegExp;
final QuillDialogTheme? dialogTheme;
final QuillToolbarVideoConfig? videoConfig;
}

View File

@ -1,57 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/internal.dart';
import 'config/video.dart';
class SelectVideoSourceDialog extends StatelessWidget {
const SelectVideoSourceDialog({super.key});
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(minHeight: 200),
width: double.infinity,
child: SingleChildScrollView(
child: Column(
children: [
ListTile(
title: Text(context.loc.gallery),
subtitle: Text(
context.loc.pickAVideoFromYourGallery,
),
leading: const Icon(Icons.photo_sharp),
onTap: () => Navigator.of(context).pop(InsertVideoSource.gallery),
),
ListTile(
title: Text(context.loc.camera),
subtitle: Text(context.loc.recordAVideoUsingYourCamera),
leading: const Icon(Icons.camera),
enabled: !isDesktopApp,
onTap: () => Navigator.of(context).pop(InsertVideoSource.camera),
),
ListTile(
title: Text(context.loc.link),
subtitle: Text(
context.loc.pasteAVideoUsingALink,
),
leading: const Icon(Icons.link),
onTap: () => Navigator.of(context).pop(InsertVideoSource.link),
),
],
),
),
);
}
}
Future<InsertVideoSource?> showSelectVideoSourceDialog({
required BuildContext context,
}) async {
final imageSource = await showModalBottomSheet<InsertVideoSource>(
showDragHandle: true,
context: context,
constraints: const BoxConstraints(maxWidth: 640),
builder: (context) => const SelectVideoSourceDialog(),
);
return imageSource;
}

View File

@ -1,134 +1 @@
import 'package:flutter/material.dart'; // TODO Implement this library.
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/internal.dart';
import 'package:image_picker/image_picker.dart';
import '../../common/default_video_insert.dart';
import '../../common/image_video_utils.dart';
import '../quill_simple_toolbar_api.dart';
import 'config/video.dart';
import 'config/video_config.dart';
import 'select_video_source.dart';
// ignore: invalid_use_of_internal_member
class QuillToolbarVideoButton extends QuillToolbarBaseButtonStateless {
const QuillToolbarVideoButton({
required super.controller,
QuillToolbarVideoButtonOptions? options,
/// Shares common options between all buttons, prefer the [options]
/// over the [baseOptions].
super.baseOptions,
super.key,
}) : _options = options,
super(options: options);
final QuillToolbarVideoButtonOptions? _options;
@override
QuillToolbarVideoButtonOptions? get options => _options;
void _sharedOnPressed(BuildContext context) {
_onPressedHandler(context);
afterButtonPressed(context);
}
Future<void> _handleVideoInsert(String videoUrl) async {
await handleVideoInsert(
videoUrl,
controller: controller,
onVideoInsertCallback: options?.videoConfig?.onVideoInsertCallback,
onVideoInsertedCallback: options?.videoConfig?.onVideoInsertedCallback,
);
}
Future<void> _onPressedHandler(BuildContext context) async {
final onRequestPickVideo = options?.videoConfig?.onRequestPickVideo;
if (onRequestPickVideo != null) {
final videoUrl = await onRequestPickVideo(context);
if (videoUrl != null) {
await _handleVideoInsert(videoUrl);
}
return;
}
final imageSource = await showSelectVideoSourceDialog(context: context);
if (imageSource == null) {
return;
}
final videoUrl = switch (imageSource) {
InsertVideoSource.gallery =>
(await ImagePicker().pickVideo(source: ImageSource.gallery))?.path,
InsertVideoSource.camera =>
(await ImagePicker().pickVideo(source: ImageSource.camera))?.path,
InsertVideoSource.link =>
context.mounted ? await _typeLink(context) : null,
};
if (videoUrl == null) {
return;
}
if (videoUrl.trim().isNotEmpty) {
_handleVideoInsert(videoUrl);
}
}
Future<String?> _typeLink(BuildContext context) async {
final value = await showDialog<String>(
context: context,
builder: (_) => TypeLinkDialog(
dialogTheme: options?.dialogTheme,
linkType: LinkType.video,
),
);
return value;
}
@override
Widget buildButton(BuildContext context) {
return QuillToolbarIconButton(
icon: Icon(
iconData(context),
size: iconSize(context) * iconButtonFactor(context),
),
tooltip: tooltip(context),
isSelected: false,
onPressed: () => _sharedOnPressed(context),
iconTheme: iconTheme(context),
);
}
@override
Widget? buildCustomChildBuilder(BuildContext context) {
return childBuilder?.call(
QuillToolbarVideoButtonOptions(
afterButtonPressed: afterButtonPressed(context),
iconData: iconData(context),
dialogTheme: options?.dialogTheme,
iconSize: iconSize(context),
iconButtonFactor: iconButtonFactor(context),
linkRegExp: options?.linkRegExp,
tooltip: tooltip(context),
iconTheme: options?.iconTheme,
videoConfig: options?.videoConfig,
),
QuillToolbarVideoButtonExtraOptions(
context: context,
controller: controller,
onPressed: () => _sharedOnPressed(context),
),
);
}
@override
IconData Function(BuildContext context) get getDefaultIconData =>
(context) => Icons.movie_creation;
@override
String Function(BuildContext context) get getDefaultTooltip =>
(context) => context.loc.insertVideo;
}

View File

@ -1,244 +0,0 @@
import 'dart:convert';
import 'dart:io' as io show Directory, File;
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_quill/flutter_quill_internal.dart';
import 'package:flutter_quill/quill_delta.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:frontend/Screens/myTemplates/quill_delta_sample.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:path/path.dart' as path;
import 'package:responsive_builder/responsive_builder.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_user_travel.dart';
class Template extends StatefulWidget {
final Map<String, dynamic>? templateData;
const Template({super.key, required this.templateData});
static Template fromState(GoRouterState state) {
return Template(templateData: state.extra as Map<String, dynamic>?);
}
@override
TemplateState createState() => TemplateState();
}
class TemplateState extends State<Template> {
final ApiService apiService = ApiService();
// final QuillController _controller = QuillController.basic();
Color layoutColor = Colors.redAccent;
Color bodyColor = Colors.white;
final Map<String, TextEditingController> controllers = {};
List<String> dataHeader = ["subject"];
Map<String, dynamic> get TemplateData {
final data = {
// "org_id": orgId;
"template_name": controllers["templateName"]?.text,
"subject": controllers["subject"]?.text,
"body_html": controllers["bodyData"]?.text,
"placeholder": [],
// "created_by": userId
};
// Only add group_id if it's an edit operation
// if (widget.templateData != null && widget.templateData!.containsKey('group_id')) {
// data["template_id"] = templateData;
// }
return data;
}
@override
void initState() {
super.initState();
for (var field in dataHeader) {
controllers[field] = TextEditingController();
}
updateData();
loadInitialData();
}
@override
// void dispose() {
// // controllers.dispose();
// // _editorScrollController.dispose();
// _editorFocusNode.dispose();
// super.dispose();
// }
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;
});
}
Future<void> updateData() async {
// Ensure apiselectedUser is not null before printing
if (widget.templateData != null) {
print("API Selected User Has Data - ${widget.templateData}");
print(
"API Selected User Has - ${widget.templateData?["templateData"]?["subject"]}");
setState(() {
// ✅ Wrap in setState to update the UI
controllers["subject"]?.text =
widget.templateData?["templateData"]?["subject"] ?? "";
// if (widget.group?["international_policy_id"] != null) {
// selectedInternational =
// widget.group!["international_policy_id"].toString();
// }
});
} else {
print("API Selected User Has Data - No data available yet");
}
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: const Color(0xFFf5f5f5),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding: isDesktop
? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(
child: buildUserTable(
isDesktop, context, bodyColor, layoutColor)),
],
),
),
);
});
}
Widget buildUserTable(
bool isDesktop, context, Color? bodyColor, Color layoutColor) {
return Container(
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Editor"),
SizedBox(
height: 20,
),
buildTempalteSubject(isDesktop),
IconButton(
icon: const Icon(Icons.output),
tooltip: 'Print Delta JSON to log',
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text(
'The JSON Delta has been printed to the console.')));
},
),
SizedBox(
height: 20,
),
buildTempalteBody(isDesktop)
],
),
),
);
}
Widget buildTempalteSubject(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Subject",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["subject"],
onChanged: (value) {
// _clearError("local_id_num");
},
decoration: InputDecoration(
labelText: "enter the subject",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildTempalteBody(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Content",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
const SizedBox(height: 10),
],
);
}
}

View File

@ -1,9 +1,19 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io' as io show Directory, File; import 'dart:io' as io show Directory, File;
import 'package:delta_to_html/delta_to_html.dart';
import 'package:flutter/cupertino.dart' as dom;
import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:flutter_quill/quill_delta.dart';
import 'package:flutter_quill/quill_delta.dart' as quill;
import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart';
import 'package:html2md/html2md.dart' as html2md;
import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart';
import 'package:flutter_quill/flutter_quill.dart' as quill;
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' as dom hide Element, Text;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -11,18 +21,20 @@ import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_quill/flutter_quill_internal.dart'; import 'package:flutter_quill/flutter_quill_internal.dart';
import 'package:flutter_quill/quill_delta.dart'; import 'package:flutter_quill/quill_delta.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:frontend/Screens/myTemplates/quill_delta_sample.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:responsive_builder/responsive_builder.dart'; import 'package:responsive_builder/responsive_builder.dart';
import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart'; import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart'; import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart'; import '../../services/apiService.dart';
import '../../utils/auth_utils.dart'; import '../../utils/auth_utils.dart';
import '../../widgets/custom_user_travel.dart'; import '../../widgets/custom_user_travel.dart';
import 'dialog_placeholders.dart';
class Template extends StatefulWidget { class Template extends StatefulWidget {
final Map<String, dynamic>? templateData; final Map<String, dynamic>? templateData;
@ -41,21 +53,44 @@ class TemplateState extends State<Template> {
final ApiService apiService = ApiService(); final ApiService apiService = ApiService();
// final QuillController _controller = QuillController.basic(); // final QuillController _controller = QuillController.basic();
String? orgId;
String? userId;
Color layoutColor = Colors.redAccent; Color layoutColor = Colors.redAccent;
Color bodyColor = Colors.white; Color bodyColor = Colors.white;
final Map<String, TextEditingController> controllers = {}; final Map<String, TextEditingController> controllers = {};
List<String> dataHeader = ["subject"]; List<String> dataHeader = ["subject"];
List<String> placeholders = [];
late QuillController _controller = QuillController.basic();
final FocusNode _focusNode = FocusNode();
late int templateId = 0;
late String templateName = "";
late List<Map<String, dynamic>> placeholderList = [];
Map<String, dynamic> get TemplateData { Map<String, dynamic> get TemplateData {
final data = { final data = {
// "org_id": orgId; "org_id": orgId,
"template_name": controllers["templateName"]?.text,
// "template_id": templateId,
// "template_name": controllers["templateName"]?.text,
"template_id": templateId,
"template_name": templateName,
"subject": controllers["subject"]?.text, "subject": controllers["subject"]?.text,
"body_html": controllers["bodyData"]?.text, "body_html": DeltaToHTML.encodeJson(
"placeholder": [], _controller.document.toDelta().toJson(),
),
// "body_html": jsonEncode(_controller.document.toDelta().toJson()),
// "body_html": _controller,
// "body_html": convertQuillDocToHtml(_controller.document),
// ✅ convert delta to HTML
"placeholder": jsonEncode(placeholderList),
// "created_by": userId // "created_by": userId
}; };
print('start 123');
print(jsonEncode(_controller.document.toDelta().toJson()));
// print(jsonEncode(_controller.document));
// Only add group_id if it's an edit operation // Only add group_id if it's an edit operation
// if (widget.templateData != null && widget.templateData!.containsKey('group_id')) { // if (widget.templateData != null && widget.templateData!.containsKey('group_id')) {
// data["template_id"] = templateData; // data["template_id"] = templateData;
@ -73,7 +108,7 @@ class TemplateState extends State<Template> {
} }
updateData(); updateData();
loadinitializeData();
loadInitialData(); loadInitialData();
} }
@ -84,32 +119,255 @@ class TemplateState extends State<Template> {
// _editorFocusNode.dispose(); // _editorFocusNode.dispose();
// super.dispose(); // super.dispose();
// } // }
void loadinitializeData() async {
orgId = await getOrgId();
userId = await getUserId();
}
void loadInitialData() async { void loadInitialData() async {
String? layoutString = await getLayoutColor(); String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor(); String? bodyStringColor = await getBodyColor();
setState(() { setState(() {
layoutColor = layoutString != null layoutColor =
layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyStringColor != null bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
} }
String convertQuillDocToHtml(quill.Document doc) {
final buffer = StringBuffer();
print("convertQuillDocToHtml");
for (final op in doc.toDelta().toList()) {
final insert = op.data;
final attrs = op.attributes ?? {};
if (insert is String) {
var content = insert;
// Handle formatting (bold, italic, etc.)
if (attrs.containsKey('bold')) {
content = '<strong>$content</strong>';
}
if (attrs.containsKey('italic')) {
content = '<em>$content</em>';
}
// Wrap each paragraph with <p>
if (content.trim().isNotEmpty) {
buffer.write('<p>${content.trim()}</p>');
}
}
}
return buffer.toString();
}
String convertQuillDocToHtml2(quill.Document doc) {
final buffer = StringBuffer();
final lines = <String>[];
final delta = doc.toDelta();
String applyStyles(String text, Map<String, dynamic>? attrs) {
if (attrs == null) return text;
if (attrs.containsKey('bold')) {
text = '<strong>$text</strong>';
}
if (attrs.containsKey('italic')) {
text = '<em>$text</em>';
}
return text;
}
for (final op in delta.toList()) {
final insert = op.data;
final attrs = op.attributes;
if (insert is String) {
final parts = insert.split('\n');
for (int i = 0; i < parts.length; i++) {
final part = applyStyles(parts[i], attrs);
lines.add(part);
if (i < parts.length - 1) {
// End of line: wrap accumulated content into <p>
final joined = lines.join('');
if (joined.trim().isNotEmpty) {
buffer.writeln('<p>${joined.trim()}</p>');
}
lines.clear();
}
}
}
}
// Add remaining lines
final joined = lines.join('');
if (joined.trim().isNotEmpty) {
buffer.writeln('<p>${joined.trim()}</p>');
}
return buffer.toString();
}
String extractPlainTextFromHtml(String html) {
final regex = RegExp(r'<p>(.*?)<\/p>', multiLine: true, dotAll: true);
final matches = regex.allMatches(html);
final buffer = StringBuffer();
for (final match in matches) {
final text = match.group(1)?.replaceAll(RegExp(r'<[^>]*>'), '') ?? '';
buffer.writeln(text.trim());
}
return buffer.toString();
}
String decodeHtmlEntities(String text) {
return text
.replaceAll('&nbsp;', ' ')
.replaceAll('&amp;', '&')
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replaceAll('&quot;', '"')
.replaceAll('&#39;', "'"); // add more as needed
}
// quill.Document convertSimpleHtmlToQuill(String htmlString) {
// final delta = quill.Delta();
// final doc = html_parser.parse(htmlString);
// final body = doc.body;
//
// void walk(Node node) {
// if (node is Text) {
// delta.insert(node.text);
// } else if (node is Element) {
// switch (node.localName) {
// case 'p':
// node.nodes.forEach(walk);
// delta.insert('\n');
// break;
// case 'br':
// delta.insert('\n');
// break;
// case 'strong':
// case 'b':
// delta.insert(node.text, {'bold': true});
// break;
// case 'em':
// case 'i':
// delta.insert(node.text, {'italic': true});
// break;
// case 'a':
// delta.insert(node.text, {'link': node.attributes['href']});
// break;
// default:
// node.nodes.forEach(walk);
// }
// }
// }
//
// if (body != null) {
// walk(body);
// }
//
// return quill.Document.fromDelta(delta..insert('\n'));
// }
String formatTemplateName(String input) {
return input
.split('_') // split by underscore
.map(
(word) =>
word.isNotEmpty
? '${word[0].toUpperCase()}${word.substring(1)}'
: '',
)
.join(' ');
}
Future<void> updateData() async { Future<void> updateData() async {
// Ensure apiselectedUser is not null before printing // Ensure apiselectedUser is not null before printing
if (widget.templateData != null) { if (widget.templateData != null) {
print("API Selected User Has Data - ${widget.templateData}"); print("API Selected User Has Data - ${widget.templateData}");
print( print(
"API Selected User Has - ${widget.templateData?["templateData"]?["subject"]}"); "API Selected User Has - ${widget.templateData?["templateData"]?["subject"]}",
);
setState(() { setState(() {
// ✅ Wrap in setState to update the UI // ✅ Wrap in setState to update the UI
controllers["templateName"]?.text =
widget.templateData?["templateData"]?["template_name"] ?? "";
controllers["subject"]?.text = controllers["subject"]?.text =
widget.templateData?["templateData"]?["subject"] ?? ""; widget.templateData?["templateData"]?["subject"] ?? "";
final bodyHtml =
widget.templateData?["templateData"]?["body_html"] ?? "";
print("bodyHtml - $bodyHtml");
String html = widget.templateData?["templateData"]?["body_html"] ?? "";
final htmlToDelta = HtmlToDelta();
// Convert the HTML string to Quill Delta format
// This is where the magic happens, but also where complex HTML might be simplified
final quill.Delta initialDelta = htmlToDelta.convert(html);
// Create a Quill Document from the Delta
final quill.Document quillDoc = quill.Document.fromDelta(initialDelta);
// Initialize the QuillController with the converted document
_controller = quill.QuillController(
document: quillDoc,
selection: const TextSelection.collapsed(
offset: 0,
), // Cursor at the start
);
// final plainText = extractPlainTextFromHtml(bodyHtml);
// final decodedText = decodeHtmlEntities(plainText);
// final quillDoc = quill.Document()..insert(0, decodedText);
// _controller = quill.QuillController(
// document: quillDoc,
// selection: const TextSelection.collapsed(offset: 0),
// );
templateName =
widget.templateData?["templateData"]?["template_name"] ?? "";
print("Fetched template_name: $templateName");
final rawPlaceholder =
widget.templateData?["templateData"]?["placeholder"];
if (rawPlaceholder is String) {
// If it's a JSON string, decode it first
placeholderList = List<Map<String, dynamic>>.from(
jsonDecode(rawPlaceholder),
);
} else if (rawPlaceholder is List) {
// If it's already a list (ideal case)
placeholderList = List<Map<String, dynamic>>.from(rawPlaceholder);
}
print("Extracted placeholders: $placeholders");
print("Fetched placeholders: $placeholderList");
templateId =
int.tryParse(
widget.templateData?["templateData"]?["template_id"]
?.toString() ??
'0',
) ??
0;
print("Fetched template_id: $templateId");
// if (widget.group?["international_policy_id"] != null) { // if (widget.group?["international_policy_id"] != null) {
// selectedInternational = // selectedInternational =
@ -121,21 +379,92 @@ class TemplateState extends State<Template> {
} }
} }
Future<void> handleSubmit() async {
Map<String, dynamic> data = TemplateData;
print('TemplateData - $data');
// final String deltaJsonString = TemplateData?["body_html"] ?? "[]";
//
// print('deltaJsonString $deltaJsonString');
//
// // Decode the JSON string into a list
// final List<dynamic> deltaJson = jsonDecode(deltaJsonString);
//
// print(DeltaToHTML.encodeJson(deltaJson));
//
// // Then convert it to a Quill document
// final doc = Document.fromJson(List<Map<String, dynamic>>.from(deltaJson));
//
// // Set it to the controller
// _controller = QuillController(
// document: doc,
// selection: const TextSelection.collapsed(offset: 0),
// );
// print('doc JSON: ${jsonEncode(doc.toDelta().toJson())}');
// print('doc plain text: ${doc.toPlainText()}');
// print('doc $doc');
setState(() {
updateTemplateData(data);
// This triggers UI rebuild with error messages
// if (validateData()) {
// postGroupData();
// }
});
}
Future<void> updateTemplateData(policyData) async {
final String apiUrldata = '$apiUrl/api/template/update/${templateId}';
final token = await getToken(); // Fetch token
if (token == null) {
throw Exception('Token not found. Please log in.');
}
try {
final response = await http.put(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode(policyData), // Convert map to JSON
);
if (response.statusCode == 200) {
print("policyData submitted successfully!");
print("Response: ${response.body}");
context.go('/templateList');
} else {
print("Failed to submit policyData. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting policyData: $e");
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFf5f5f5), backgroundColor: const Color(0xFFf5f5f5),
appBar: CustomAppBar(isDesktop: isDesktop), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding:
isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height * vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
@ -144,19 +473,29 @@ class TemplateState extends State<Template> {
// if (isDesktop) CustomDrawer(isDesktop: true), // if (isDesktop) CustomDrawer(isDesktop: true),
Expanded( Expanded(
child: buildUserTable( child: buildUserTable(
isDesktop, context, bodyColor, layoutColor)), isDesktop,
context,
bodyColor,
layoutColor,
),
),
], ],
), ),
), ),
); );
}); },
);
} }
Widget buildUserTable( Widget buildUserTable(
bool isDesktop, context, Color? bodyColor, Color layoutColor) { bool isDesktop,
context,
Color? bodyColor,
Color layoutColor,
) {
return Container( return Container(
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null, margin: isDesktop ? const EdgeInsets.only(top: 5.0, bottom: 10.0) : null,
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(28),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDesktop ? Colors.white : Color(0xFFFCFCFC), color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
), ),
@ -165,24 +504,22 @@ class TemplateState extends State<Template> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text("Editor"), Text(
SizedBox( formatTemplateName(templateName),
height: 20, style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w400,
color: Colors.black,
), ),
),
SizedBox(height: 10),
buildTempalteSubject(isDesktop), buildTempalteSubject(isDesktop),
IconButton(
icon: const Icon(Icons.output), SizedBox(height: 10),
tooltip: 'Print Delta JSON to log', buildTempalteBody(isDesktop),
onPressed: () { Spacer(),
ScaffoldMessenger.of(context).showSnackBar(const SnackBar( buildActions(isDesktop),
content: Text(
'The JSON Delta has been printed to the console.')));
},
),
SizedBox(
height: 20,
),
buildTempalteBody(isDesktop)
], ],
), ),
), ),
@ -196,11 +533,16 @@ class TemplateState extends State<Template> {
Text( Text(
"Subject", "Subject",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper( CustomTextFieldUserTravellerWrapper(
isFocused: false, isFocused: false,
color: Colors.white,
width: isDesktop ? MediaQuery.of(context).size.width * 0.8 : null,
isDesktop: isDesktop, isDesktop: isDesktop,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
@ -211,9 +553,11 @@ class TemplateState extends State<Template> {
// _clearError("local_id_num"); // _clearError("local_id_num");
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: "enter the subject", labelText: "Enter the subject",
labelStyle: labelStyle: GoogleFonts.poppins(
GoogleFonts.poppins(fontSize: 12, color: Colors.grey), fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -233,11 +577,138 @@ class TemplateState extends State<Template> {
"Content", "Content",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w600,
color: Colors.black, color: Color(0xFF575A74),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Container(
color: Color(0xFFFFFEF0),
width: isDesktop ? MediaQuery.of(context).size.width * 0.8 : null,
child: IconTheme(
data: IconThemeData(size: 18), // Set icon size here
child: QuillSimpleToolbar(controller: _controller),
),
),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
final selected = await showDialog(
context: context,
builder:
(context) =>
PlaceholdersModal(placeholders: placeholderList),
);
if (selected != null) {
print("User selected placeholder: $selected");
// You can now insert into a controller or editor
// Ensure the editor is focused
FocusScope.of(context).requestFocus(_focusNode);
final selection = _controller.selection;
final position = selection.baseOffset;
// if (position >= 0) {
// final intPosition = position.toInt();
//
// _controller.document.insert(intPosition, selected);
//
// _controller.updateSelection(
// TextSelection.collapsed(
// offset: intPosition + selected.length,
// ),
// ChangeSource.local,
// );
// }
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.grey,
// padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
child: Text(
'Placeholders',
style: GoogleFonts.poppins(fontSize: 13, color: Colors.black),
),
),
],
),
),
Container(
padding: const EdgeInsets.all(16),
height: MediaQuery.of(context).size.height * 0.3,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Color(0xFFD6D5E6), width: 0.5),
),
child: QuillEditor(
controller: _controller,
scrollController: ScrollController(),
focusNode: _focusNode,
),
),
],
);
}
Widget buildActions(bool isDesktop) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
child: ElevatedButton(
onPressed: () {
context.go('/templateList');
// You can get text from commentController.text
Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
foregroundColor: layoutColor,
// backgroundColor: widget.layoutColor,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
child: Text(
'Cancel',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
color: layoutColor,
),
),
),
),
SizedBox(width: 10),
SizedBox(
child: ElevatedButton(
onPressed: () {
handleSubmit();
// You can get text from commentController.text
// Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
backgroundColor: layoutColor,
// backgroundColor: widget.layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
'Save',
style: GoogleFonts.poppins(fontSize: 14, color: Colors.white),
),
),
),
], ],
); );
} }

View File

@ -0,0 +1,830 @@
import 'dart:convert';
import 'dart:io' as io show Directory, File;
import 'package:delta_to_html/delta_to_html.dart';
import 'package:flutter/cupertino.dart' as dom;
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill/quill_delta.dart';
import 'package:flutter_quill/quill_delta.dart' as quill;
import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:frontend/Screens/myTemplates/templateForex.dart'
as _editorFocusNode;
import 'package:frontend/Screens/myTemplates/templateForex.dart'
as _editorScrollController;
import 'package:frontend/Screens/myTemplates/templateForex.dart' as _controller;
import 'package:html2md/html2md.dart' as html2md;
import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart';
import 'package:flutter_quill/flutter_quill.dart' as quill;
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' as dom hide Element, Text;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_quill/flutter_quill_internal.dart';
import 'package:flutter_quill/quill_delta.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
import 'package:responsive_builder/responsive_builder.dart';
import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_user_travel.dart';
import 'dialog_placeholders.dart';
class TemplateForex extends StatefulWidget {
final Map<String, dynamic>? templateData;
const TemplateForex({super.key, required this.templateData});
static TemplateForex fromState(GoRouterState state) {
return TemplateForex(templateData: state.extra as Map<String, dynamic>?);
}
@override
TemplateForexState createState() => TemplateForexState();
}
class TemplateForexState extends State<TemplateForex> {
final ApiService apiService = ApiService();
// final QuillController _controller = QuillController.basic();
String? orgId;
String? userId;
Color layoutColor = Colors.redAccent;
Color bodyColor = Colors.white;
final Map<String, TextEditingController> controllers = {};
List<String> dataHeader = ["subject"];
List<String> placeholders = [];
late QuillController _controller = QuillController.basic();
final FocusNode _focusNode = FocusNode();
late int templateId = 0;
late String templateName = "";
late List<Map<String, dynamic>> placeholderList = [];
Map<String, dynamic> get TemplateData {
final data = {
"org_id": orgId,
// "template_id": templateId,
// "template_name": controllers["templateName"]?.text,
"template_id": templateId,
"template_name": templateName,
"subject": controllers["subject"]?.text,
"body_html": DeltaToHTML.encodeJson(
_controller.document.toDelta().toJson(),
),
// "body_html": jsonEncode(_controller.document.toDelta().toJson()),
// "body_html": _controller,
// "body_html": convertQuillDocToHtml(_controller.document),
// ✅ convert delta to HTML
"placeholder": jsonEncode(placeholderList),
// "created_by": userId
};
print('start 123');
print(jsonEncode(_controller.document.toDelta().toJson()));
// print(jsonEncode(_controller.document));
// Only add group_id if it's an edit operation
// if (widget.templateData != null && widget.templateData!.containsKey('group_id')) {
// data["template_id"] = templateData;
// }
return data;
}
@override
void initState() {
super.initState();
for (var field in dataHeader) {
controllers[field] = TextEditingController();
}
updateData();
loadinitializeData();
loadInitialData();
}
@override
// void dispose() {
// // controllers.dispose();
// // _editorScrollController.dispose();
// _editorFocusNode.dispose();
// super.dispose();
// }
void loadinitializeData() async {
orgId = await getOrgId();
userId = await getUserId();
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor();
setState(() {
layoutColor =
layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
});
}
String convertQuillDocToHtml(quill.Document doc) {
final buffer = StringBuffer();
print("convertQuillDocToHtml");
for (final op in doc.toDelta().toList()) {
final insert = op.data;
final attrs = op.attributes ?? {};
if (insert is String) {
var content = insert;
// Handle formatting (bold, italic, etc.)
if (attrs.containsKey('bold')) {
content = '<strong>$content</strong>';
}
if (attrs.containsKey('italic')) {
content = '<em>$content</em>';
}
// Wrap each paragraph with <p>
if (content.trim().isNotEmpty) {
buffer.write('<p>${content.trim()}</p>');
}
}
}
return buffer.toString();
}
String convertQuillDocToHtml2(quill.Document doc) {
final buffer = StringBuffer();
final lines = <String>[];
final delta = doc.toDelta();
String applyStyles(String text, Map<String, dynamic>? attrs) {
if (attrs == null) return text;
if (attrs.containsKey('bold')) {
text = '<strong>$text</strong>';
}
if (attrs.containsKey('italic')) {
text = '<em>$text</em>';
}
return text;
}
for (final op in delta.toList()) {
final insert = op.data;
final attrs = op.attributes;
if (insert is String) {
final parts = insert.split('\n');
for (int i = 0; i < parts.length; i++) {
final part = applyStyles(parts[i], attrs);
lines.add(part);
if (i < parts.length - 1) {
// End of line: wrap accumulated content into <p>
final joined = lines.join('');
if (joined.trim().isNotEmpty) {
buffer.writeln('<p>${joined.trim()}</p>');
}
lines.clear();
}
}
}
}
// Add remaining lines
final joined = lines.join('');
if (joined.trim().isNotEmpty) {
buffer.writeln('<p>${joined.trim()}</p>');
}
return buffer.toString();
}
String extractPlainTextFromHtml(String html) {
final regex = RegExp(r'<p>(.*?)<\/p>', multiLine: true, dotAll: true);
final matches = regex.allMatches(html);
final buffer = StringBuffer();
for (final match in matches) {
final text = match.group(1)?.replaceAll(RegExp(r'<[^>]*>'), '') ?? '';
buffer.writeln(text.trim());
}
return buffer.toString();
}
String decodeHtmlEntities(String text) {
return text
.replaceAll('&nbsp;', ' ')
.replaceAll('&amp;', '&')
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replaceAll('&quot;', '"')
.replaceAll('&#39;', "'"); // add more as needed
}
// quill.Document convertSimpleHtmlToQuill(String htmlString) {
// final delta = quill.Delta();
// final doc = html_parser.parse(htmlString);
// final body = doc.body;
//
// void walk(Node node) {
// if (node is Text) {
// delta.insert(node.text);
// } else if (node is Element) {
// switch (node.localName) {
// case 'p':
// node.nodes.forEach(walk);
// delta.insert('\n');
// break;
// case 'br':
// delta.insert('\n');
// break;
// case 'strong':
// case 'b':
// delta.insert(node.text, {'bold': true});
// break;
// case 'em':
// case 'i':
// delta.insert(node.text, {'italic': true});
// break;
// case 'a':
// delta.insert(node.text, {'link': node.attributes['href']});
// break;
// default:
// node.nodes.forEach(walk);
// }
// }
// }
//
// if (body != null) {
// walk(body);
// }
//
// return quill.Document.fromDelta(delta..insert('\n'));
// }
String formatTemplateName(String input) {
return input
.split('_') // split by underscore
.map(
(word) =>
word.isNotEmpty
? '${word[0].toUpperCase()}${word.substring(1)}'
: '',
)
.join(' ');
}
Future<void> updateData() async {
// Ensure apiselectedUser is not null before printing
if (widget.templateData != null) {
print("API Selected User Has Data - ${widget.templateData}");
print(
"API Selected User Has - ${widget.templateData?["templateData"]?["subject"]}",
);
setState(() {
// ✅ Wrap in setState to update the UI
controllers["templateName"]?.text =
widget.templateData?["templateData"]?["template_name"] ?? "";
controllers["subject"]?.text =
widget.templateData?["templateData"]?["subject"] ?? "";
final bodyHtml =
widget.templateData?["templateData"]?["body_html"] ?? "";
print("bodyHtml - $bodyHtml");
String html = widget.templateData?["templateData"]?["body_html"] ?? "";
final htmlToDelta = HtmlToDelta();
// Convert the HTML string to Quill Delta format
// This is where the magic happens, but also where complex HTML might be simplified
final quill.Delta initialDelta = htmlToDelta.convert(html);
// Create a Quill Document from the Delta
final quill.Document quillDoc = quill.Document.fromDelta(initialDelta);
// Initialize the QuillController with the converted document
_controller = quill.QuillController(
document: quillDoc,
selection: const TextSelection.collapsed(
offset: 0,
), // Cursor at the start
);
// final plainText = extractPlainTextFromHtml(bodyHtml);
// final decodedText = decodeHtmlEntities(plainText);
// final quillDoc = quill.Document()..insert(0, decodedText);
// _controller = quill.QuillController(
// document: quillDoc,
// selection: const TextSelection.collapsed(offset: 0),
// );
templateName =
widget.templateData?["templateData"]?["template_name"] ?? "";
print("Fetched template_name: $templateName");
final rawPlaceholder =
widget.templateData?["templateData"]?["placeholder"];
if (rawPlaceholder is String) {
// If it's a JSON string, decode it first
placeholderList = List<Map<String, dynamic>>.from(
jsonDecode(rawPlaceholder),
);
} else if (rawPlaceholder is List) {
// If it's already a list (ideal case)
placeholderList = List<Map<String, dynamic>>.from(rawPlaceholder);
}
print("Extracted placeholders: $placeholders");
print("Fetched placeholders: $placeholderList");
templateId =
int.tryParse(
widget.templateData?["templateData"]?["template_id"]
?.toString() ??
'0',
) ??
0;
print("Fetched template_id: $templateId");
// if (widget.group?["international_policy_id"] != null) {
// selectedInternational =
// widget.group!["international_policy_id"].toString();
// }
});
} else {
print("API Selected User Has Data - No data available yet");
}
}
Future<void> handleSubmit() async {
Map<String, dynamic> data = TemplateData;
print('TemplateData - $data');
// final String deltaJsonString = TemplateData?["body_html"] ?? "[]";
//
// print('deltaJsonString $deltaJsonString');
//
// // Decode the JSON string into a list
// final List<dynamic> deltaJson = jsonDecode(deltaJsonString);
//
// print(DeltaToHTML.encodeJson(deltaJson));
//
// // Then convert it to a Quill document
// final doc = Document.fromJson(List<Map<String, dynamic>>.from(deltaJson));
//
// // Set it to the controller
// _controller = QuillController(
// document: doc,
// selection: const TextSelection.collapsed(offset: 0),
// );
// print('doc JSON: ${jsonEncode(doc.toDelta().toJson())}');
// print('doc plain text: ${doc.toPlainText()}');
// print('doc $doc');
setState(() {
updateTemplateData(data);
// This triggers UI rebuild with error messages
// if (validateData()) {
// postGroupData();
// }
});
}
Future<void> updateTemplateData(policyData) async {
final String apiUrldata = '$apiUrl/api/template/update/${templateId}';
final token = await getToken(); // Fetch token
if (token == null) {
throw Exception('Token not found. Please log in.');
}
try {
final response = await http.put(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode(policyData), // Convert map to JSON
);
if (response.statusCode == 200) {
print("policyData submitted successfully!");
print("Response: ${response.body}");
context.go('/templateList');
} else {
print("Failed to submit policyData. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting policyData: $e");
}
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: const Color(0xFFf5f5f5),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding:
isDesktop
? EdgeInsets.symmetric(
horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(
child: buildUserTable(
isDesktop,
context,
bodyColor,
layoutColor,
),
),
],
),
),
);
},
);
}
Widget buildUserTable(
bool isDesktop,
context,
Color? bodyColor,
Color layoutColor,
) {
return Container(
margin: isDesktop ? const EdgeInsets.only(top: 5.0, bottom: 10.0) : null,
padding: const EdgeInsets.all(28),
decoration: BoxDecoration(
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
formatTemplateName(templateName),
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
// SizedBox(height: 10),
// buildTempalteSubject(isDesktop),
//
// SizedBox(height: 10),
buildTempalteBody(isDesktop),
Spacer(),
buildActions(isDesktop),
],
),
),
);
}
Widget buildTempalteSubject(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Subject",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
color: Colors.white,
width: isDesktop ? MediaQuery.of(context).size.width * 0.8 : null,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["subject"],
onChanged: (value) {
// _clearError("local_id_num");
},
decoration: InputDecoration(
labelText: "Enter the subject",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildTempalteBody(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text(
// "Content",
// style: GoogleFonts.poppins(
// fontSize: 12,
// fontWeight: FontWeight.w600,
// color: Color(0xFF575A74),
// ),
// ),
const SizedBox(height: 10),
Container(
color: Color(0xFFFFFEF0),
width: isDesktop ? MediaQuery.of(context).size.width * 0.8 : null,
child: IconTheme(
data: IconThemeData(size: 18), // Set icon size here
child: QuillSimpleToolbar(
controller: _controller,
config: QuillSimpleToolbarConfig(
embedButtons: FlutterQuillEmbeds.toolbarButtons(),
showClipboardPaste: true,
customButtons: [
QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.add_alarm_rounded),
onPressed: () {
_controller.document.insert(
_controller.selection.extentOffset,
TimeStampEmbed(DateTime.now().toString()),
);
_controller.updateSelection(
TextSelection.collapsed(
offset: _controller.selection.extentOffset + 1,
),
ChangeSource.local,
);
},
),
],
buttonOptions: QuillSimpleToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: () {
final isDesktop = {
TargetPlatform.linux,
TargetPlatform.windows,
TargetPlatform.macOS,
}.contains(defaultTargetPlatform);
// if (isDesktop) {
// _editorFocusNode.requestFocus();
// }
},
),
linkStyle: QuillToolbarLinkStyleButtonOptions(
validateLink: (link) {
// Treats all links as valid. When launching the URL,
// `https://` is prefixed if the link is incomplete (e.g., `google.com` → `https://google.com`)
// however this happens only within the editor.
return true;
},
),
),
),
),
),
),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
final selected = await showDialog(
context: context,
builder:
(context) =>
PlaceholdersModal(placeholders: placeholderList),
);
if (selected != null) {
print("User selected placeholder: $selected");
// You can now insert into a controller or editor
// Ensure the editor is focused
FocusScope.of(context).requestFocus(_focusNode);
final selection = _controller.selection;
final position = selection.baseOffset;
// if (position >= 0) {
// final intPosition = position.toInt();
//
// _controller.document.insert(intPosition, selected);
//
// _controller.updateSelection(
// TextSelection.collapsed(
// offset: intPosition + selected.length,
// ),
// ChangeSource.local,
// );
// }
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.grey,
// padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
child: Text(
'Placeholders',
style: GoogleFonts.poppins(fontSize: 13, color: Colors.black),
),
),
],
),
),
Container(
padding: const EdgeInsets.all(16),
height: MediaQuery.of(context).size.height * 0.5,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Color(0xFFD6D5E6), width: 0.5),
),
child: QuillEditor(
controller: _controller,
scrollController: ScrollController(),
focusNode: _focusNode,
config: QuillEditorConfig(
placeholder: 'Start writing your notes...',
padding: const EdgeInsets.all(16),
embedBuilders: [
...FlutterQuillEmbeds.editorBuilders(
imageEmbedConfig: QuillEditorImageEmbedConfig(
imageProviderBuilder: (context, imageUrl) {
// https://pub.dev/packages/flutter_quill_extensions#-image-assets
if (imageUrl.startsWith('assets/')) {
return AssetImage(imageUrl);
}
return null;
},
),
videoEmbedConfig: QuillEditorVideoEmbedConfig(
customVideoBuilder: (videoUrl, readOnly) {
// To load YouTube videos https://github.com/singerdmx/flutter-quill/releases/tag/v10.8.0
return null;
},
),
),
TimeStampEmbedBuilder(),
],
),
),
),
],
);
}
Widget buildActions(bool isDesktop) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
child: ElevatedButton(
onPressed: () {
context.go('/OrganizationSettings');
// You can get text from commentController.text
Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
foregroundColor: layoutColor,
// backgroundColor: widget.layoutColor,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
child: Text(
'Cancel',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
color: layoutColor,
),
),
),
),
SizedBox(width: 10),
SizedBox(
child: ElevatedButton(
onPressed: () {
handleSubmit();
// You can get text from commentController.text
// Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
backgroundColor: layoutColor,
// backgroundColor: widget.layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
'Save',
style: GoogleFonts.poppins(fontSize: 14, color: Colors.white),
),
),
),
],
);
}
}
@override
void dispose() {
_controller.dispose();
_editorScrollController.dispose();
_editorFocusNode.dispose();
}
class TimeStampEmbed extends Embeddable {
const TimeStampEmbed(String value) : super(timeStampType, value);
static const String timeStampType = 'timeStamp';
static TimeStampEmbed fromDocument(Document document) =>
TimeStampEmbed(jsonEncode(document.toDelta().toJson()));
Document get document => Document.fromJson(jsonDecode(data));
}
class TimeStampEmbedBuilder extends EmbedBuilder {
@override
String get key => 'timeStamp';
@override
String toPlainText(Embed node) {
return node.value.data;
}
@override
Widget build(BuildContext context, EmbedContext embedContext) {
return Row(
children: [
const Icon(Icons.access_time_rounded),
Text(embedContext.node.value.data as String),
],
);
}
}

View File

@ -0,0 +1,37 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
final QuillController _controller = QuillController.basic();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("title")),
body: Column(
children: [
QuillSimpleToolbar(controller: _controller),
Expanded(
child: Container(
padding: const EdgeInsets.all(16),
child: QuillEditor(
controller: _controller,
scrollController: ScrollController(),
focusNode: _focusNode,
),
),
),
],
),
);
}
}

View File

@ -10,6 +10,7 @@ import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart'; import 'package:http_parser/http_parser.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:responsive_builder/responsive_builder.dart'; import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../config/apiUrl.dart'; import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart'; import '../../routes/custom_appBar.dart';
@ -66,11 +67,13 @@ class _OrgSetUpState extends State<OrgSetUp> {
String? bodyStringColor = await getBodyColor(); String? bodyStringColor = await getBodyColor();
setState(() { setState(() {
layoutColor = layoutString != null layoutColor =
layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyStringColor != null bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
@ -99,7 +102,7 @@ class _OrgSetUpState extends State<OrgSetUp> {
"created_by": null, "created_by": null,
"updated_by": null, "updated_by": null,
"is_active": 1 "is_active": 1,
// "org_id": orgId, // "org_id": orgId,
// "created_by": userId, // "created_by": userId,
@ -144,10 +147,14 @@ class _OrgSetUpState extends State<OrgSetUp> {
try { try {
print("getUpdatedServices"); print("getUpdatedServices");
final result = await apiService.fetchOrganization(); final prefs = await SharedPreferences.getInstance();
print("UUPdatedServices - $result"); final String? orgDataString = prefs.getString('org_data');
if (orgDataString != null) {
final Map<String, dynamic> orgData = jsonDecode(orgDataString);
print("UUPdatedServices - $orgData");
setState(() { setState(() {
selectedOrg = result; selectedOrg = orgData;
String? rawLogoPath = selectedOrg?['logo']; String? rawLogoPath = selectedOrg?['logo'];
if (rawLogoPath != null && rawLogoPath.contains('/assets')) { if (rawLogoPath != null && rawLogoPath.contains('/assets')) {
@ -158,16 +165,27 @@ class _OrgSetUpState extends State<OrgSetUp> {
_orgNameController.text = selectedOrg?['name']; _orgNameController.text = selectedOrg?['name'];
layoutColor = selectedOrg?['layout_color'] != null layoutColor =
? Color(int.parse( selectedOrg?['layout_color'] != null
selectedOrg!['layout_color'].toString().replaceFirst('0x', ''), ? Color(
radix: 16)) int.parse(
selectedOrg!['layout_color'].toString().replaceFirst(
'0x',
'',
),
radix: 16,
),
)
: Colors.white; : Colors.white;
bodyColor = selectedOrg?['color'] != null bodyColor =
? Color(int.parse( selectedOrg?['color'] != null
? Color(
int.parse(
selectedOrg!['color'].toString().replaceFirst('0x', ''), selectedOrg!['color'].toString().replaceFirst('0x', ''),
radix: 16)) radix: 16,
),
)
: Colors.blue; : Colors.blue;
// Set mail config fields // Set mail config fields
@ -200,12 +218,11 @@ class _OrgSetUpState extends State<OrgSetUp> {
services = []; services = [];
} }
selectedServiceIds = services.map<Map<String, dynamic>>((item) { selectedServiceIds =
services.map<Map<String, dynamic>>((item) {
// force cast or copy to a regular map // force cast or copy to a regular map
final map = Map<String, dynamic>.from(item); final map = Map<String, dynamic>.from(item);
return { return {"service_id": map['service_id'].toString()};
"service_id": map['service_id'].toString(),
};
}).toList(); }).toList();
}); });
@ -213,6 +230,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
print("selectedOrg - $selectedOrg"); print("selectedOrg - $selectedOrg");
print("mailConfig - $mailConfig"); print("mailConfig - $mailConfig");
}
// final result = await apiService.fetchOrganization();
} catch (e) { } catch (e) {
print('Error fetching updatedServices list: $e'); print('Error fetching updatedServices list: $e');
} }
@ -222,10 +242,7 @@ class _OrgSetUpState extends State<OrgSetUp> {
errorMessages.clear(); // Reset errors errorMessages.clear(); // Reset errors
// Required fields that must not be empty // Required fields that must not be empty
List<String> requiredFields = [ List<String> requiredFields = ["name", "description"];
"name",
"description",
];
// Check validation for each field // Check validation for each field
for (String field in requiredFields) { for (String field in requiredFields) {
@ -298,8 +315,27 @@ class _OrgSetUpState extends State<OrgSetUp> {
if (response.statusCode == 200 || response.statusCode == 201) { if (response.statusCode == 200 || response.statusCode == 201) {
print("✅ User submitted successfully!"); print("✅ User submitted successfully!");
print("📨 Response: ${response.body}"); print("📨 Response Organizt Update: ${response.body}");
context.go('/listPlan');
final data = json.decode(response.body);
if (!data.containsKey('data') || data['data'] is! Map) {
throw Exception(
"Invalid response format: 'data' field is missing or not a Map",
);
}
// Make sure each item is a Map<String, dynamic>
final Map<String, dynamic> orgList = Map<String, dynamic>.from(
data['data'],
);
print(orgList);
await updateOrgDataWithNewValues(orgList);
print("📨 Response Organizt Update:");
// return orgList;
context.go('/OrganizationSettings');
// context.go('/listPlan');
} else { } else {
print("❌ Submission failed. Status: ${response.statusCode}"); print("❌ Submission failed. Status: ${response.statusCode}");
print("📨 Body: ${response.body}"); print("📨 Body: ${response.body}");
@ -309,6 +345,39 @@ class _OrgSetUpState extends State<OrgSetUp> {
} }
} }
Future<void> updateOrgDataWithNewValues(Map<String, dynamic> newData) async {
final prefs = await SharedPreferences.getInstance();
final String? orgDataString = prefs.getString('org_data');
Map<String, dynamic> orgData = {};
if (orgDataString != null) {
try {
orgData = jsonDecode(orgDataString);
layoutColor =
orgData['layout_color'] != null
? Color(
int.parse(
orgData['layout_color'].toString().replaceFirst('0x', ''),
radix: 16,
),
)
: Colors.white;
} catch (e) {
print('❌ Failed to decode org_data: $e');
}
}
// Merge in the new data
orgData.addAll(newData);
// Save back
await prefs.setString('org_data', jsonEncode(orgData));
await prefs.setString('layout_color', orgData['layout_color']);
print("✅ Updated org_data saved.");
}
void handleSubmit() { void handleSubmit() {
print("HandleSubmiy - $orgData"); print("HandleSubmiy - $orgData");
createOrgData(orgData); createOrgData(orgData);
@ -327,8 +396,10 @@ class _OrgSetUpState extends State<OrgSetUp> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
// backgroundColor: Colors.white, // backgroundColor: Colors.white,
@ -336,23 +407,27 @@ class _OrgSetUpState extends State<OrgSetUp> {
appBar: CustomAppBar(isDesktop: isDesktop), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding:
isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height * vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
child: Row( child: Row(
children: [ children: [
// if (isDesktop) CustomDrawer(isDesktop: true), // if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildOrganizationLayout(isDesktop)) Expanded(child: buildOrganizationLayout(isDesktop)),
], ],
), ),
), ),
); );
}); },
);
} }
Widget buildOrganizationLayout(isDesktop) { Widget buildOrganizationLayout(isDesktop) {
@ -382,28 +457,49 @@ class _OrgSetUpState extends State<OrgSetUp> {
Container( Container(
padding: const EdgeInsets.all(5), padding: const EdgeInsets.all(5),
color: Colors.white, color: Colors.white,
child: isDesktop child:
isDesktop
? Row( ? Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
// children: [Text("Button")], // children: [Text("Button")],
children: children: _buildSubmit(
_buildSubmit(isDesktop, isViewMode, layoutColor), isDesktop,
isViewMode,
layoutColor,
),
) )
: Row( : Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: children: _buildSubmit(
_buildSubmit(isDesktop, isViewMode, layoutColor), isDesktop,
)) isViewMode,
layoutColor,
),
),
),
], ],
), ),
); );
} }
Widget buildOrgLayout(bool isDesktop) { Widget buildOrgLayout(bool isDesktop) {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
final double responsiveLogoWidth =
screenWidth * 0.15; // 15% of screen width
final double responsiveLogoHeight =
screenHeight * 0.07; // 7% of screen height
final double largeResponsiveLogoWidth =
screenWidth * 0.6; // 60% of screen width
final double largeResponsiveLogoHeight = screenHeight * 0.15;
Future<void> _pickImage() async { Future<void> _pickImage() async {
final picker = ImagePicker(); final picker = ImagePicker();
final XFile? pickedFile = final XFile? pickedFile = await picker.pickImage(
await picker.pickImage(source: ImageSource.gallery); source: ImageSource.gallery,
);
if (pickedFile != null && kIsWeb) { if (pickedFile != null && kIsWeb) {
try { try {
@ -424,9 +520,11 @@ class _OrgSetUpState extends State<OrgSetUp> {
// margin: isDesktop // margin: isDesktop
// ? EdgeInsets.all(10.0) // ? EdgeInsets.all(10.0)
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
height: isDesktop height:
isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
// decoration: BoxDecoration( // decoration: BoxDecoration(
// border: isDesktop // border: isDesktop
// ? Border.all( // ? Border.all(
@ -446,7 +544,11 @@ class _OrgSetUpState extends State<OrgSetUp> {
children: [ children: [
Container( Container(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 20, right: 20, bottom: 20, top: 5), left: 20,
right: 20,
bottom: 20,
top: 5,
),
// height: MediaQuery.of(context).size.height * 0.8, // height: MediaQuery.of(context).size.height * 0.8,
color: Colors.white, color: Colors.white,
child: Column( child: Column(
@ -463,7 +565,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
? "Update Organization" ? "Update Organization"
: "Create Organization", : "Create Organization",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 15, fontWeight: FontWeight.w500), fontSize: 15,
fontWeight: FontWeight.w500,
),
), ),
], ],
), ),
@ -472,7 +576,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
color: Colors.white, color: Colors.white,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, // now -> .center , old -> .start crossAxisAlignment:
CrossAxisAlignment
.center, // now -> .center , old -> .start
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(top: 1.0), padding: const EdgeInsets.only(top: 1.0),
@ -481,7 +587,8 @@ class _OrgSetUpState extends State<OrgSetUp> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121)), color: Color(0xFF212121),
),
), ),
), ),
SizedBox(width: 8), SizedBox(width: 8),
@ -496,7 +603,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Enter Organization Name", hintText: "Enter Organization Name",
hintStyle: GoogleFonts.poppins( hintStyle: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey), fontSize: 14,
color: Colors.grey,
),
floatingLabelBehavior: floatingLabelBehavior:
FloatingLabelBehavior.never, FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
@ -510,25 +619,35 @@ class _OrgSetUpState extends State<OrgSetUp> {
Spacer(), Spacer(),
GestureDetector( GestureDetector(
onTap: _pickImage, onTap: _pickImage,
child: _imageBytes != null
child:
_imageBytes != null
? ClipOval( ? ClipOval(
child: Image.memory( child: Image.memory(
_imageBytes!, _imageBytes!,
width: 50, // width: 50,
height: 50, // height: 50,
fit: BoxFit.cover, width:
responsiveLogoWidth, // Use responsive width
height: responsiveLogoHeight,
fit: BoxFit.contain,
), ),
) )
: selectedOrg?['logo'] != null : selectedOrg?['logo'] != null
? ClipRect( ? ClipRect(
child: Image.network( child: Image.network(
selectedOrg!['logo'], selectedOrg!['logo'],
width: 250, // increased width:
height: 75, // increased responsiveLogoWidth, // Use responsive width
height: responsiveLogoHeight,
// width: 250,
// height: 55,
fit: BoxFit.contain, fit: BoxFit.contain,
errorBuilder: errorBuilder: (
(context, error, stackTrace) { context,
error,
stackTrace,
) {
return const CircleAvatar( return const CircleAvatar(
radius: 20, radius: 20,
backgroundColor: Colors.redAccent, backgroundColor: Colors.redAccent,
@ -547,30 +666,33 @@ class _OrgSetUpState extends State<OrgSetUp> {
), ),
), ),
SizedBox( SizedBox(height: 10),
height: 10,
),
Text( Text(
"Services", "Services",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121)), color: Color(0xFF212121),
), ),
SizedBox(
height: 10,
), ),
SizedBox(height: 10),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: Color(0xFFF4F4FB)), border: Border.all(color: Color(0xFFF4F4FB)),
borderRadius: BorderRadius.circular(1), borderRadius: BorderRadius.circular(1),
// color: bodyColor, // color: bodyColor,
// color: Color(0xFFF5F5F5), // color: Color(0xFFF5F5F5),
color: Colors.white), color: Colors.white,
padding: ),
EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 5), padding: EdgeInsets.only(
child: isDesktop left: 5,
right: 5,
top: 15,
bottom: 5,
),
child:
isDesktop
? Row( ? Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// mainAxisSize: MainAxisSize.min, // mainAxisSize: MainAxisSize.min,
@ -579,15 +701,11 @@ class _OrgSetUpState extends State<OrgSetUp> {
: Expanded( : Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row( child: Row(children: _buildOptions()),
children: _buildOptions(),
), ),
), ),
), ),
), SizedBox(height: 15),
SizedBox(
height: 15,
),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -597,7 +715,8 @@ class _OrgSetUpState extends State<OrgSetUp> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121)), color: Color(0xFF212121),
),
), ),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -606,18 +725,26 @@ class _OrgSetUpState extends State<OrgSetUp> {
// color: Color(0xFFF4F4FB), // color: Color(0xFFF4F4FB),
), ),
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: 5, right: 5, top: 15, bottom: 5), left: 5,
child: layoutColor != null && bodyColor != null right: 5,
top: 15,
bottom: 5,
),
child:
layoutColor != null && bodyColor != null
? ColorThemePickerWidget( ? ColorThemePickerWidget(
initialLayoutColor: layoutColor, initialLayoutColor: layoutColor,
initialBodyColor: bodyColor, initialBodyColor: bodyColor,
onLayoutColorSelected: onLayoutColorSelected: (
(Color selectedLayoutColor) { Color selectedLayoutColor,
) {
setState(() { setState(() {
layoutColor = selectedLayoutColor; layoutColor = selectedLayoutColor;
}); });
}, },
onBodyColorSelected: (Color selectedBodyColor) { onBodyColorSelected: (
Color selectedBodyColor,
) {
setState(() { setState(() {
bodyColor = selectedBodyColor; bodyColor = selectedBodyColor;
}); });
@ -628,9 +755,7 @@ class _OrgSetUpState extends State<OrgSetUp> {
], ],
), ),
SizedBox( SizedBox(height: 15),
height: 15,
),
Container( Container(
color: Colors.white, color: Colors.white,
child: Column( child: Column(
@ -643,7 +768,8 @@ class _OrgSetUpState extends State<OrgSetUp> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF212121)), color: Color(0xFF212121),
),
), ),
// GestureDetector( // GestureDetector(
@ -660,11 +786,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
// ), // ),
], ],
), ),
// if (showMail)
SizedBox( // if (showMail)
height: 10, SizedBox(height: 10),
),
Container( Container(
// width: double.infinity, // width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -678,7 +802,8 @@ class _OrgSetUpState extends State<OrgSetUp> {
// color: Color(0xFFF5F5F5), // color: Color(0xFFF5F5F5),
), ),
child: Row( child: Row(
mainAxisAlignment: isDesktop mainAxisAlignment:
isDesktop
? MainAxisAlignment.start ? MainAxisAlignment.start
: MainAxisAlignment.center, : MainAxisAlignment.center,
children: [ children: [
@ -688,17 +813,18 @@ class _OrgSetUpState extends State<OrgSetUp> {
initialMailData: mailConfig, initialMailData: mailConfig,
onMailDataChanged: (updatedData) { onMailDataChanged: (updatedData) {
// You can setState here or do something else with updatedData // You can setState here or do something else with updatedData
print( print("Updated Mail Data: $updatedData");
"Updated Mail Data: $updatedData");
mailConfig = updatedData; mailConfig = updatedData;
}, },
) )
: CircularProgressIndicator(), : CircularProgressIndicator(),
], ],
)) ),
),
], ],
)), ),
),
// isDesktop // isDesktop
// ? Row( // ? Row(
// mainAxisAlignment: MainAxisAlignment.end, // mainAxisAlignment: MainAxisAlignment.end,
@ -738,8 +864,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
String serviceId = service['service_id'].toString(); String serviceId = service['service_id'].toString();
// bool isSelected = selectedServiceIds.contains(serviceId); // bool isSelected = selectedServiceIds.contains(serviceId);
bool isSelected = bool isSelected = selectedServiceIds.any(
selectedServiceIds.any((item) => item["service_id"] == serviceId); (item) => item["service_id"] == serviceId,
);
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
@ -747,8 +874,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
String serviceId = service['service_id'].toString(); String serviceId = service['service_id'].toString();
// Check if already selected // Check if already selected
int existingIndex = selectedServiceIds int existingIndex = selectedServiceIds.indexWhere(
.indexWhere((item) => item["service_id"] == serviceId); (item) => item["service_id"] == serviceId,
);
if (existingIndex != -1) { if (existingIndex != -1) {
selectedServiceIds.removeAt(existingIndex); selectedServiceIds.removeAt(existingIndex);
@ -757,24 +885,30 @@ class _OrgSetUpState extends State<OrgSetUp> {
} }
}); });
}, },
child: Row(children: [ child: Row(
children: [
iconUrl.isNotEmpty iconUrl.isNotEmpty
? Image.network( ? Image.network(
iconUrl, iconUrl,
width: 18, width: 18,
height: 18, height: 18,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
return Icon(fallbackIcon, return Icon(
size: 18, fallbackIcon,
color: isSelected == name
? Color(0xFF114D8B)
: Color(0xFF475569));
},
)
: Icon(fallbackIcon,
size: 18, size: 18,
color: color:
isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569)), isSelected == name
? Color(0xFF114D8B)
: Color(0xFF475569),
);
},
)
: Icon(
fallbackIcon,
size: 18,
color:
isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569),
),
SizedBox(width: 2), SizedBox(width: 2),
@ -784,7 +918,8 @@ class _OrgSetUpState extends State<OrgSetUp> {
fontSize: 12, fontSize: 12,
color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569),
fontWeight: fontWeight:
isSelected == name ? FontWeight.bold : FontWeight.w500), isSelected == name ? FontWeight.bold : FontWeight.w500,
),
// fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)),
), ),
@ -796,15 +931,19 @@ class _OrgSetUpState extends State<OrgSetUp> {
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: isSelected ? Colors.green : Colors.grey, width: 1), color: isSelected ? Colors.green : Colors.grey,
width: 1,
),
), ),
child: Icon( child: Icon(
Icons.check_circle, Icons.check_circle,
size: 10, size: 10,
color: isSelected ? Colors.green : Colors.grey, color: isSelected ? Colors.green : Colors.grey,
// color: Colors.grey, // color: Colors.grey,
)), ),
]), ),
],
),
); );
} }
@ -848,13 +987,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
onPressed: () { onPressed: () {
context.go('/listPlan'); context.go('/listPlan');
}, },
child: Text( child: Text("Cancel", style: GoogleFonts.poppins(fontSize: 12)),
"Cancel",
style: GoogleFonts.poppins(fontSize: 12),
)),
SizedBox(
width: 20,
), ),
SizedBox(width: 20),
MouseRegion( MouseRegion(
// cursor: widget.isViewMode // cursor: widget.isViewMode
// ? SystemMouseCursors.forbidden // ? SystemMouseCursors.forbidden
@ -873,12 +1008,9 @@ class _OrgSetUpState extends State<OrgSetUp> {
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), ),
onPressed: handleSubmit, // Disable when in view mode onPressed: handleSubmit, // Disable when in view mode
child: Text( child: Text("Submit", style: GoogleFonts.poppins(fontSize: 12)),
"Submit",
style: GoogleFonts.poppins(fontSize: 12),
), ),
), ),
)
]; ];
} }
} }

View File

@ -57,7 +57,8 @@ class _ColorThemePickerWidgetState extends State<ColorThemePickerWidget> {
children: [ children: [
// Layout Color Picker // Layout Color Picker
GestureDetector( GestureDetector(
onTap: () => _showColorPickerDialog( onTap:
() => _showColorPickerDialog(
title: "Choose Layout Color", title: "Choose Layout Color",
colors: layoutThemeColors, colors: layoutThemeColors,
onColorSelected: (color) { onColorSelected: (color) {
@ -68,24 +69,26 @@ class _ColorThemePickerWidgetState extends State<ColorThemePickerWidget> {
}, },
), ),
child: _buildColorBox( child: _buildColorBox(
selectedLayoutColor ?? Colors.grey.shade300, Icons.palette), selectedLayoutColor ?? Colors.grey.shade300,
Icons.palette,
), ),
SizedBox(width: 15),
// Body Color Picker
GestureDetector(
onTap: () => _showColorPickerDialog(
title: "Choose Body Color",
colors: bodyThemeColors,
onColorSelected: (color) {
setState(() {
selectedBodyColor = color; // low opacity
});
widget.onBodyColorSelected(selectedBodyColor!);
},
),
child: _buildColorBox(
selectedBodyColor ?? Colors.grey.shade300, Icons.opacity),
), ),
// SizedBox(width: 15),
// // Body Color Picker
// GestureDetector(
// onTap: () => _showColorPickerDialog(
// title: "Choose Body Color",
// colors: bodyThemeColors,
// onColorSelected: (color) {
// setState(() {
// selectedBodyColor = color; // low opacity
// });
// widget.onBodyColorSelected(selectedBodyColor!);
// },
// ),
// child: _buildColorBox(
// selectedBodyColor ?? Colors.grey.shade300, Icons.opacity),
// ),
], ],
); );
} }
@ -110,12 +113,14 @@ class _ColorThemePickerWidgetState extends State<ColorThemePickerWidget> {
}) { }) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder:
(context) => AlertDialog(
title: Text(title), title: Text(title),
content: Wrap( content: Wrap(
spacing: 10, spacing: 10,
runSpacing: 10, runSpacing: 10,
children: colors.map((color) { children:
colors.map((color) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
onColorSelected(color); onColorSelected(color);

File diff suppressed because it is too large Load Diff

View File

@ -39,8 +39,8 @@ class DynamicItinerary extends StatefulWidget {
final GlobalKey<FlightScreenState> flightScreenKey; final GlobalKey<FlightScreenState> flightScreenKey;
final ValueNotifier<String?> tripTypeNotifier; final ValueNotifier<String?> tripTypeNotifier;
const DynamicItinerary( const DynamicItinerary({
{super.key, super.key,
required this.apiData, required this.apiData,
required this.onItineraryUpdate, required this.onItineraryUpdate,
required this.apiCountryData, required this.apiCountryData,
@ -51,7 +51,8 @@ class DynamicItinerary extends StatefulWidget {
this.tripType, this.tripType,
this.apiDataForClass, this.apiDataForClass,
required this.tripTypeNotifier, required this.tripTypeNotifier,
required this.flightScreenKey}); required this.flightScreenKey,
});
@override @override
DynamicItineraryState createState() => DynamicItineraryState(); DynamicItineraryState createState() => DynamicItineraryState();
@ -135,7 +136,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
if (rawServices != null && rawServices is String) { if (rawServices != null && rawServices is String) {
try { try {
List<dynamic> decoded = json.decode(rawServices); List<dynamic> decoded = json.decode(rawServices);
List<Map<String, String>> formatted = decoded List<Map<String, String>> formatted =
decoded
.map((e) => {"service_id": e['service_id'].toString()}) .map((e) => {"service_id": e['service_id'].toString()})
.toList(); .toList();
@ -168,7 +170,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
"insurance", "insurance",
"visa", "visa",
"miscellaneous", "miscellaneous",
"taxi" "taxi",
]; ];
} else { } else {
// tripType is null or not 1/2, allow everything // tripType is null or not 1/2, allow everything
@ -242,21 +244,25 @@ class DynamicItineraryState extends State<DynamicItinerary> {
final selectedIds = final selectedIds =
selectedOrgServiceIds.map((e) => e['service_id']).toSet(); selectedOrgServiceIds.map((e) => e['service_id']).toSet();
final additionalServices = selectedAllServices!.where((service) { final additionalServices =
selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase(); final name = (service['name'] ?? "").toString().toLowerCase();
final id = service['service_id'].toString(); final id = service['service_id'].toString();
final isNameAllowed = final isNameAllowed =
allowedServiceNames.isEmpty || allowedServiceNames.contains(name); allowedServiceNames.isEmpty ||
allowedServiceNames.contains(name);
return filledItineraryKeys.contains(name) && return filledItineraryKeys.contains(name) &&
!selectedIds.contains(id) && !selectedIds.contains(id) &&
isNameAllowed; isNameAllowed;
}).toList(); }).toList();
final originalFiltered = selectedAllServices!.where((service) { final originalFiltered =
selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase(); final name = (service['name'] ?? "").toString().toLowerCase();
final id = service['service_id'].toString(); final id = service['service_id'].toString();
final isNameAllowed = final isNameAllowed =
allowedServiceNames.isEmpty || allowedServiceNames.contains(name); allowedServiceNames.isEmpty ||
allowedServiceNames.contains(name);
return selectedIds.contains(id) && isNameAllowed; return selectedIds.contains(id) && isNameAllowed;
}).toList(); }).toList();
@ -266,21 +272,25 @@ class DynamicItineraryState extends State<DynamicItinerary> {
}); });
print( print(
"Services chosen based on filled keys + selected: $ServicesChoosed"); "Services chosen based on filled keys + selected: $ServicesChoosed",
);
} else { } else {
final selectedIds = final selectedIds =
selectedOrgServiceIds.map((e) => e['service_id']).toSet(); selectedOrgServiceIds.map((e) => e['service_id']).toSet();
final filtered = selectedAllServices!.where((service) { final filtered =
selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase(); final name = (service['name'] ?? "").toString().toLowerCase();
final isNameAllowed = final isNameAllowed =
allowedServiceNames.isEmpty || allowedServiceNames.contains(name); allowedServiceNames.isEmpty ||
allowedServiceNames.contains(name);
return selectedIds.contains(service['service_id'].toString()) && return selectedIds.contains(service['service_id'].toString()) &&
isNameAllowed; isNameAllowed;
}).toList(); }).toList();
setState(() { setState(() {
ServicesChoosed = filtered ServicesChoosed =
filtered
..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0));
}); });
@ -296,23 +306,32 @@ class DynamicItineraryState extends State<DynamicItinerary> {
setState(() { setState(() {
itineraryData = { itineraryData = {
"Train": List<Map<String, dynamic>>.from( "Train": List<Map<String, dynamic>>.from(
widget.selectedPlanData['train'] ?? []), widget.selectedPlanData['train'] ?? [],
),
"Bus": List<Map<String, dynamic>>.from( "Bus": List<Map<String, dynamic>>.from(
widget.selectedPlanData['bus'] ?? []), widget.selectedPlanData['bus'] ?? [],
),
"Taxi": List<Map<String, dynamic>>.from( "Taxi": List<Map<String, dynamic>>.from(
widget.selectedPlanData['taxi'] ?? []), widget.selectedPlanData['taxi'] ?? [],
),
"Miscellaneous": List<Map<String, dynamic>>.from( "Miscellaneous": List<Map<String, dynamic>>.from(
widget.selectedPlanData['miscellaneous'] ?? []), widget.selectedPlanData['miscellaneous'] ?? [],
),
"Flight": List<Map<String, dynamic>>.from( "Flight": List<Map<String, dynamic>>.from(
widget.selectedPlanData['flight'] ?? []), widget.selectedPlanData['flight'] ?? [],
),
"Accomodation": List<Map<String, dynamic>>.from( "Accomodation": List<Map<String, dynamic>>.from(
widget.selectedPlanData['accomodation'] ?? []), widget.selectedPlanData['accomodation'] ?? [],
),
"Insurance": List<Map<String, dynamic>>.from( "Insurance": List<Map<String, dynamic>>.from(
widget.selectedPlanData['insurance'] ?? []), widget.selectedPlanData['insurance'] ?? [],
),
"Visa": List<Map<String, dynamic>>.from( "Visa": List<Map<String, dynamic>>.from(
widget.selectedPlanData['visa'] ?? []), widget.selectedPlanData['visa'] ?? [],
),
"Forex": List<Map<String, dynamic>>.from( "Forex": List<Map<String, dynamic>>.from(
widget.selectedPlanData['forex'] ?? []), widget.selectedPlanData['forex'] ?? [],
),
}; };
}); });
} else { } else {
@ -330,7 +349,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
"accomodation", "accomodation",
"insurance", "insurance",
"visa", "visa",
"forex" "forex",
]; ];
// for (String key in keys) { // for (String key in keys) {
@ -416,7 +435,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
if (existingId != null && existingId != 0) { if (existingId != null && existingId != 0) {
// int itemId = itemList.indexWhere((item) => item["id"] == existingId); // int itemId = itemList.indexWhere((item) => item["id"] == existingId);
int itemId = itemList.indexWhere( int itemId = itemList.indexWhere(
(item) => item[idKey]?.toString() == existingId.toString()); (item) => item[idKey]?.toString() == existingId.toString(),
);
if (itemId != -1) { if (itemId != -1) {
print(" Updating existing item with id: $existingId"); print(" Updating existing item with id: $existingId");
@ -428,8 +448,9 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// CASE 1: Update if indx exists in list // CASE 1: Update if indx exists in list
if (existingIndex != null && existingIndex != 0) { if (existingIndex != null && existingIndex != 0) {
int itemIndex = int itemIndex = itemList.indexWhere(
itemList.indexWhere((item) => item["indx"] == existingIndex); (item) => item["indx"] == existingIndex,
);
if (itemIndex != -1) { if (itemIndex != -1) {
print("Updating existing item with indx: $existingIndex"); print("Updating existing item with indx: $existingIndex");
newData["is_active"] = "1"; newData["is_active"] = "1";
@ -494,6 +515,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
}); });
print(" onItineraryUpdate - $type - ${itineraryData[type]!} "); print(" onItineraryUpdate - $type - ${itineraryData[type]!} ");
widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
print("ItienreayDATE - $itineraryData");
} }
// void handleItineraryUpdate(String type, Map<String, dynamic> newData) { // void handleItineraryUpdate(String type, Map<String, dynamic> newData) {
@ -583,8 +605,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
onOpen: handleEdit, onOpen: handleEdit,
onAddNew: handlecreateNewPlan, onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode, isViewMode: widget.isViewMode,
onDeleteAccommodation: (data) => onDeleteAccommodation:
handleItinerarydelete("Accomodation", data), (data) => handleItinerarydelete("Accomodation", data),
); );
break; break;
case "Miscellaneous": case "Miscellaneous":
@ -594,8 +616,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
isViewMode: widget.isViewMode, isViewMode: widget.isViewMode,
onAddNew: handlecreateNewPlan, onAddNew: handlecreateNewPlan,
apiData: widget.apiData, apiData: widget.apiData,
onDeleteMiscellaneous: (data) => onDeleteMiscellaneous:
handleItinerarydelete("Miscellaneous", data), (data) => handleItinerarydelete("Miscellaneous", data),
); );
break; break;
case "Flight": case "Flight":
@ -608,7 +630,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
onAddNew: handlecreateNewPlan, onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode, isViewMode: widget.isViewMode,
apiData: widget.apiData, apiData: widget.apiData,
onDeleteFlight: (data) => handleItinerarydelete("Flight", data)); onDeleteFlight: (data) => handleItinerarydelete("Flight", data),
);
break; break;
} }
@ -621,7 +644,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
loginUser: widget.loginUser, loginUser: widget.loginUser,
onSavetrain: (data) => handleItineraryUpdate("Train", data), onSavetrain: (data) => handleItineraryUpdate("Train", data),
tripType: widget.tripType, tripType: widget.tripType,
selectedItem: selectedItem); selectedItem: selectedItem,
);
break; break;
case "Taxi": case "Taxi":
selectedWidget = TaxiScreen( selectedWidget = TaxiScreen(
@ -629,7 +653,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
apiData: widget.apiData, apiData: widget.apiData,
loginUser: widget.loginUser, loginUser: widget.loginUser,
onSavetaxi: (data) => handleItineraryUpdate("Taxi", data), onSavetaxi: (data) => handleItineraryUpdate("Taxi", data),
selectedItem: selectedItem); selectedItem: selectedItem,
);
break; break;
case "Bus": case "Bus":
selectedWidget = BusScreen( selectedWidget = BusScreen(
@ -637,7 +662,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
apiData: widget.apiData, apiData: widget.apiData,
loginUser: widget.loginUser, loginUser: widget.loginUser,
onSaveBus: (data) => handleItineraryUpdate("Bus", data), onSaveBus: (data) => handleItineraryUpdate("Bus", data),
selectedItem: selectedItem); selectedItem: selectedItem,
);
break; break;
case "Insurance": case "Insurance":
selectedWidget = InsuranceScreen( selectedWidget = InsuranceScreen(
@ -666,8 +692,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
onClose: handleClose, onClose: handleClose,
apiData: widget.apiData, apiData: widget.apiData,
loginUser: widget.loginUser, loginUser: widget.loginUser,
onSaveMiscellaneous: (data) => onSaveMiscellaneous:
handleItineraryUpdate("Miscellaneous", data), (data) => handleItineraryUpdate("Miscellaneous", data),
selectedItem: selectedItem, selectedItem: selectedItem,
selectedIndex: selectedIndex, selectedIndex: selectedIndex,
); );
@ -676,8 +702,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
selectedWidget = AccomodationScreen( selectedWidget = AccomodationScreen(
onClose: handleClose, onClose: handleClose,
loginUser: widget.loginUser, loginUser: widget.loginUser,
onSaveAccomadation: (data) => onSaveAccomadation:
handleItineraryUpdate("Accomodation", data), (data) => handleItineraryUpdate("Accomodation", data),
selectedItem: selectedItem, selectedItem: selectedItem,
flightData: itineraryData["Flight"]!, flightData: itineraryData["Flight"]!,
); );
@ -776,7 +802,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// ); // );
// }); // });
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile; bool isMobile = sizingInfo.isMobile;
return Stack( return Stack(
@ -785,7 +812,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// Second container (yellow box) // Second container (yellow box)
Container( Container(
margin: EdgeInsets.only( margin: EdgeInsets.only(
top: 40), // Push it down to make room for the tab bar top: 40,
), // Push it down to make room for the tab bar
padding: EdgeInsets.all(12), padding: EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, // Card background color: Colors.white, // Card background
@ -835,12 +863,11 @@ class DynamicItineraryState extends State<DynamicItinerary> {
), ),
], ],
), ),
child: isMobile child:
isMobile
? SingleChildScrollView( ? SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row( child: Row(children: _buildOptions()),
children: _buildOptions(),
),
) )
: Row( : Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
@ -850,11 +877,31 @@ class DynamicItineraryState extends State<DynamicItinerary> {
), ),
], ],
); );
}); },
);
}
bool hasValidItineraryEntries() {
if (ServicesChoosed == null || ServicesChoosed!.isEmpty) return false;
for (var service in ServicesChoosed!) {
final serviceName = service['name'];
final entries = itineraryData[serviceName];
// Check if there is at least one active entry (is_active == 1)
final hasActive =
entries?.any((entry) => entry['is_active'] == 1) ?? false;
if (!hasActive) {
return false; // Fail fast if any one service has no active entries
}
}
return true; // All selected services have at least one active entry
} }
List<Widget> _buildOptions() { List<Widget> _buildOptions() {
if (ServicesChoosed == null) return []; if (ServicesChoosed == null && !hasValidItineraryEntries()) return [];
if (ServicesChoosed != null && if (ServicesChoosed != null &&
ServicesChoosed!.isNotEmpty && ServicesChoosed!.isNotEmpty &&
@ -875,18 +922,28 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// } // }
return ServicesChoosed!.map((service) { return ServicesChoosed!.map((service) {
final serviceName = service['name'];
final serviceEntries = itineraryData[serviceName];
final hasActive =
serviceEntries?.any((entry) => entry['is_active'] == "1") ?? false;
print("Service1: $serviceName");
print("Entries1: $serviceEntries");
print("Has Active1: $hasActive");
return Padding( return Padding(
padding: const EdgeInsets.only(right: 20.0), padding: const EdgeInsets.only(right: 20.0),
child: _buildOption( child: _buildOption(
service, itineraryData[service['name']]?.isNotEmpty ?? false), service,
hasActive,
// itineraryData[service['name']]?.isNotEmpty ?? false,
),
); );
}).toList(); }).toList();
} }
Widget _buildOption( Widget _buildOption(Map<String, dynamic> service, bool hasData) {
Map<String, dynamic> service,
bool hasData,
) {
String name = service['name']; String name = service['name'];
String iconUrl = service['icon']; // Can be empty string String iconUrl = service['icon']; // Can be empty string
IconData fallbackIcon = _getLocalIconForService(name); IconData fallbackIcon = _getLocalIconForService(name);
@ -921,7 +978,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
return Icon( return Icon(
fallbackIcon, fallbackIcon,
size: 25, size: 25,
color: isOptionSelected color:
isOptionSelected
? Color(0xFF114D8B) ? Color(0xFF114D8B)
: Color(0xFF475569), : Color(0xFF475569),
); );
@ -930,9 +988,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
: Icon( : Icon(
fallbackIcon, fallbackIcon,
size: 25, size: 25,
color: isOptionSelected color:
? Color(0xFF114D8B) isOptionSelected ? Color(0xFF114D8B) : Color(0xFF475569),
: Color(0xFF475569),
), ),
SizedBox(height: 2), SizedBox(height: 2),
Row( Row(
@ -943,10 +1000,10 @@ class DynamicItineraryState extends State<DynamicItinerary> {
// style: GoogleFonts.poppins( fontSize: 12, // style: GoogleFonts.poppins( fontSize: 12,
// fontWeight: FontWeight.w600, // fontWeight: FontWeight.w600,
// color: Color(0xFF575A74)) // color: Color(0xFF575A74))
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: isOptionSelected color:
isOptionSelected
? Color(0xFF114D8B) ? Color(0xFF114D8B)
: Color(0xFF475569), : Color(0xFF475569),
fontFamily: "Inter", fontFamily: "Inter",
@ -964,86 +1021,87 @@ class DynamicItineraryState extends State<DynamicItinerary> {
); );
} }
Widget _buildOption1( // Widget _buildOption1(
Map<String, dynamic> service, // Map<String, dynamic> service,
bool hasData, // bool hasData,
) { // )
String name = service['name']; // {
String iconUrl = service['icon']; // Can be empty string // String name = service['name'];
// Optional: define local icon fallback if iconUrl is empty // String iconUrl = service['icon']; // Can be empty string
IconData fallbackIcon = _getLocalIconForService(name); // // Optional: define local icon fallback if iconUrl is empty
// final idMap = {"service_id": service['service_id'].toString()}; // IconData fallbackIcon = _getLocalIconForService(name);
// final isSelected = selectedServiceIds.contains(idMap); // // final idMap = {"service_id": service['service_id'].toString()};
// // final isSelected = selectedServiceIds.contains(idMap);
String serviceId = service['service_id'].toString(); //
// bool isSelected = selectedServiceIds.contains(serviceId); // String serviceId = service['service_id'].toString();
// bool isSelected = // // bool isSelected = selectedServiceIds.contains(serviceId);
// selectedServiceIds.any((item) => item["service_id"] == serviceId); // // bool isSelected =
// // selectedServiceIds.any((item) => item["service_id"] == serviceId);
return GestureDetector( //
onTap: () { // return GestureDetector(
setState(() { // onTap: () {
selectedListOption = name; // setState(() {
isSelected = false; // selectedListOption = name;
}); // isSelected = false;
}, // });
child: Row(children: [ // },
iconUrl.isNotEmpty // child: Row(children: [
? Image.network( // iconUrl.isNotEmpty
iconUrl, // ? Image.network(
width: 18, // iconUrl,
height: 18, // width: 18,
errorBuilder: (context, error, stackTrace) { // height: 18,
return Icon( // errorBuilder: (context, error, stackTrace) {
fallbackIcon, // return Icon(
size: 18, // fallbackIcon,
color: selectedListOption == name // size: 18,
? Color(0xFF114D8B) // color: selectedListOption == name
: Color(0xFF475569), // ? Color(0xFF114D8B)
); // : Color(0xFF475569),
}, // );
) // },
: Icon( // )
fallbackIcon, // : Icon(
size: 18, // fallbackIcon,
color: selectedListOption == name // size: 18,
? Color(0xFF114D8B) // color: selectedListOption == name
: Color(0xFF475569), // ? Color(0xFF114D8B)
), // : Color(0xFF475569),
// ),
SizedBox(width: 2), //
Text( // SizedBox(width: 2),
name, // Text(
style: TextStyle( // name,
fontSize: 14, // style: TextStyle(
// color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74), // fontSize: 14,
color: selectedListOption == name // // color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74),
? Color(0xFF114D8B) // color: selectedListOption == name
: Color(0xFF475569), // ? Color(0xFF114D8B)
fontFamily: "Archivo", // : Color(0xFF475569),
fontWeight: selectedListOption == name // fontFamily: "Archivo",
? FontWeight.bold // fontWeight: selectedListOption == name
: FontWeight.w500), // ? FontWeight.bold
// fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), // : FontWeight.w500),
), // // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)),
// ),
SizedBox(width: 2), //
// if (selectedListOption == title && widget.isViewMode == false) // SizedBox(width: 2),
if (hasData) // // if (selectedListOption == title && widget.isViewMode == false)
Icon(Icons.circle, size: 8, color: Colors.green // if (hasData)
// color: Colors.grey, // Icon(Icons.circle, size: 8, color: Colors.green
) // // color: Colors.grey,
// Container( // )
// height: 10, // // Container(
// width: 10, // // height: 10,
// // decoration: BoxDecoration( // // width: 10,
// // shape: BoxShape.circle, // // // decoration: BoxDecoration(
// // border: Border.all(color: Colors.green, width: 1.5), // // // shape: BoxShape.circle,
// // ), // // // border: Border.all(color: Colors.green, width: 1.5),
// child:), // // // ),
]), // // child:),
); // ]),
} // );
// }
IconData _getLocalIconForService(String name) { IconData _getLocalIconForService(String name) {
switch (name.toLowerCase()) { switch (name.toLowerCase()) {

File diff suppressed because it is too large Load Diff

View File

@ -79,14 +79,15 @@ class _PolicyState extends State<Policy> {
List<Map<String, dynamic>>? policy_details = []; List<Map<String, dynamic>>? policy_details = [];
Map<String, dynamic> get policyData { Map<String, dynamic> get policyData {
List<Map<String, dynamic>> policyDetails = policy_details!.where((service) { List<Map<String, dynamic>> policyDetails =
policy_details!.where((service) {
// Only check these specific fields for emptiness // Only check these specific fields for emptiness
final fieldsToCheck = [ final fieldsToCheck = [
'cost', 'cost',
'class', 'class',
'a1_action', 'a1_action',
'a2_action', 'a2_action',
'a3_action' 'a3_action',
]; ];
// If any of the important fields has a value, keep it // If any of the important fields has a value, keep it
@ -124,8 +125,9 @@ class _PolicyState extends State<Policy> {
loadInitialData(); loadInitialData();
if (widget.policy != null) { if (widget.policy != null) {
final details = final details = List<Map<String, dynamic>>.from(
List<Map<String, dynamic>>.from(widget.policy!['policy_details']); widget.policy!['policy_details'],
);
policyCriteriaKey.currentState?.loadPolicyDetails(details); policyCriteriaKey.currentState?.loadPolicyDetails(details);
policyCriteriaKey.currentState?.fetchTrainFlightClass(); policyCriteriaKey.currentState?.fetchTrainFlightClass();
@ -138,11 +140,13 @@ class _PolicyState extends State<Policy> {
String? bodyStringColor = await getBodyColor(); String? bodyStringColor = await getBodyColor();
setState(() { setState(() {
layoutColor = layoutString != null layoutColor =
layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyStringColor != null bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
@ -175,7 +179,8 @@ class _PolicyState extends State<Policy> {
if (rawServices != null && rawServices is String) { if (rawServices != null && rawServices is String) {
try { try {
List<dynamic> decoded = json.decode(rawServices); List<dynamic> decoded = json.decode(rawServices);
List<Map<String, String>> formatted = decoded List<Map<String, String>> formatted =
decoded
.map((e) => {"service_id": e['service_id'].toString()}) .map((e) => {"service_id": e['service_id'].toString()})
.toList(); .toList();
@ -205,16 +210,21 @@ class _PolicyState extends State<Policy> {
selectedOrgServiceIds.map((e) => e['service_id']).toSet(); selectedOrgServiceIds.map((e) => e['service_id']).toSet();
if (widget.policy != null) { if (widget.policy != null) {
final details = final details = List<Map<String, dynamic>>.from(
List<Map<String, dynamic>>.from(widget.policy!['policy_details']); widget.policy!['policy_details'],
);
print( print(
"UUFiltered Selected Services - ${widget.policy!['services_ids']} "); "UUFiltered Selected Services - ${widget.policy!['services_ids']} ",
);
// pr int("UUFiltered Selected Services - $details"); // pr int("UUFiltered Selected Services - $details");
final filtered = selectedAllServices! final filtered =
.where((service) => selectedAllServices!
selectedIds.contains(service['service_id'].toString())) .where(
(service) =>
selectedIds.contains(service['service_id'].toString()),
)
.toList(); .toList();
setState(() { setState(() {
@ -236,12 +246,15 @@ class _PolicyState extends State<Policy> {
final decoded = jsonDecode(widget.policy!['services_ids']); final decoded = jsonDecode(widget.policy!['services_ids']);
setState(() { setState(() {
services = List<Map<String, dynamic>>.from(decoded) services =
.map((service) => { List<Map<String, dynamic>>.from(decoded)
.map(
(service) => {
'service_id': service['service_id'].toString(), 'service_id': service['service_id'].toString(),
'name': service['name'].toString(), 'name': service['name'].toString(),
'order': service['order'].toString(), 'order': service['order'].toString(),
}) },
)
.toList(); .toList();
}); });
@ -250,9 +263,12 @@ class _PolicyState extends State<Policy> {
print("Filtered Selected Services Added to Policy: $ServicesChoosed"); print("Filtered Selected Services Added to Policy: $ServicesChoosed");
} else { } else {
final filtered = selectedAllServices! final filtered =
.where((service) => selectedAllServices!
selectedIds.contains(service['service_id'].toString())) .where(
(service) =>
selectedIds.contains(service['service_id'].toString()),
)
.toList(); .toList();
print("ServicesChoosedYY: $ServicesChoosed"); print("ServicesChoosedYY: $ServicesChoosed");
@ -264,13 +280,16 @@ class _PolicyState extends State<Policy> {
// ServicesChoosed = filtered; // ServicesChoosed = filtered;
services = ServicesChoosed! services =
.map((service) => { ServicesChoosed!
.map(
(service) => {
'service_id': service['service_id'].toString(), 'service_id': service['service_id'].toString(),
'name': 'name':
service['name'].toString(), // ✅ no space before 'name' service['name'].toString(), // ✅ no space before 'name'
'order': service['order'].toString(), 'order': service['order'].toString(),
}) },
)
.toList(); .toList();
}); });
@ -311,7 +330,7 @@ class _PolicyState extends State<Policy> {
print("Services - $services"); print("Services - $services");
print("USR Detail Submit - $policyData"); print("USR Detail Submit - $policyData");
policyCriteriaKey.currentState?.saveCurrentPolicy(); policyCriteriaKey.currentState?.saveCurrentPolicy(services);
// Now the full data is ready in policyDataFromChild // Now the full data is ready in policyDataFromChild
print("Submitting full policyData: $policyData"); print("Submitting full policyData: $policyData");
@ -336,7 +355,7 @@ class _PolicyState extends State<Policy> {
// Validate required fields // Validate required fields
if (data["name"] == null || data["name"].toString().trim().isEmpty) { if (data["name"] == null || data["name"].toString().trim().isEmpty) {
errorMessages["name"] = "Policy name is required."; errorMessages["name"] = "Required"; // "Policy name is required.";
} }
// Validate that either domestic or international is selected // Validate that either domestic or international is selected
@ -344,10 +363,12 @@ class _PolicyState extends State<Policy> {
final international = data["international"]?.toString() ?? "0"; final international = data["international"]?.toString() ?? "0";
print( print(
"domestic: ${data["domestic"]}, international: ${data["international"]}"); "domestic: ${data["domestic"]}, international: ${data["international"]}",
);
if (domestic != "1" && international != "1") { if (domestic != "1" && international != "1") {
errorMessages["trip_type"] = "Please select Domestic or International."; errorMessages["trip_type"] =
"Required"; // "Please select Domestic or International.";
} }
// Validate at least one policy_detail with valid content // Validate at least one policy_detail with valid content
@ -359,7 +380,7 @@ class _PolicyState extends State<Policy> {
'class', 'class',
'a1_action', 'a1_action',
'a2_action', 'a2_action',
'a3_action' 'a3_action',
]; ];
return fieldsToCheck.any((field) { return fieldsToCheck.any((field) {
final value = service[field]; final value = service[field];
@ -369,7 +390,7 @@ class _PolicyState extends State<Policy> {
if (!hasAtLeastOneDetail) { if (!hasAtLeastOneDetail) {
errorMessages["policy_details"] = errorMessages["policy_details"] =
"At least one valid policy detail is required."; "Required"; // "At least one valid policy detail is required.";
} }
return errorMessages.isEmpty; return errorMessages.isEmpty;
@ -424,19 +445,24 @@ class _PolicyState extends State<Policy> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFf5f5f5), backgroundColor: Color(0xFFf5f5f5),
appBar: CustomAppBar(isDesktop: isDesktop), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding:
isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height * vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(8), : EdgeInsets.all(8),
@ -446,7 +472,6 @@ class _PolicyState extends State<Policy> {
child: Row( child: Row(
children: [ children: [
// if (isDesktop) CustomDrawer(isDesktop: true), // if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildData(isDesktop, context)), Expanded(child: buildData(isDesktop, context)),
// Expanded( // Expanded(
// child: Container( // child: Container(
@ -493,7 +518,8 @@ class _PolicyState extends State<Policy> {
// ], // ],
// ), // ),
); );
}); },
);
} }
Widget buildData(bool isDesktop, context) { Widget buildData(bool isDesktop, context) {
@ -524,7 +550,8 @@ class _PolicyState extends State<Policy> {
Container( Container(
color: Colors.white, color: Colors.white,
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: isDesktop child:
isDesktop
? Row( ? Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: _buildSubmit(isDesktop), children: _buildSubmit(isDesktop),
@ -544,7 +571,8 @@ class _PolicyState extends State<Policy> {
// margin: isDesktop // margin: isDesktop
// ? EdgeInsets.all(10.0) // ? EdgeInsets.all(10.0)
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
height: isDesktop height:
isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
// decoration: BoxDecoration( // decoration: BoxDecoration(
@ -582,7 +610,8 @@ class _PolicyState extends State<Policy> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
], ],
), ),
@ -593,7 +622,8 @@ class _PolicyState extends State<Policy> {
isDesktop ? SizedBox(height: 0) : SizedBox(height: 5), isDesktop ? SizedBox(height: 0) : SizedBox(height: 5),
Container( Container(
padding: isDesktop ? const EdgeInsets.only(left: 35) : null, padding: isDesktop ? const EdgeInsets.only(left: 35) : null,
child: isDesktop child:
isDesktop
? Row( ? Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -611,18 +641,13 @@ class _PolicyState extends State<Policy> {
], ],
), ),
), ),
SizedBox( SizedBox(height: 10),
height: 10, Divider(thickness: 0.1, color: Colors.grey),
),
Divider(
thickness: 0.1,
color: Colors.grey,
),
if (errorMessages["policy_details"] != null) ...[ if (errorMessages["policy_details"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text(
errorMessages["policy_details"]!, errorMessages["policy_details"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 10), style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
), ),
], ],
isDesktop isDesktop
@ -636,15 +661,17 @@ class _PolicyState extends State<Policy> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
),
), ),
Text( Text(
"Service Priority", "Service Priority",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74)), color: Color(0xFF575A74),
) ),
),
], ],
), ),
) )
@ -666,11 +693,7 @@ class _PolicyState extends State<Policy> {
_buildPolicyCategory(isDesktop), _buildPolicyCategory(isDesktop),
], ],
), ),
Column( Column(children: [_buildPolicyCategoryList(isDesktop)]),
children: [
_buildPolicyCategoryList(isDesktop),
],
),
], ],
), ),
), ),
@ -719,11 +742,14 @@ class _PolicyState extends State<Policy> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Policy Name", Text(
"Policy Name *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74))), color: Color(0xFF575A74),
),
),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -736,8 +762,10 @@ class _PolicyState extends State<Policy> {
onChanged: (value) => _clearError("name"), onChanged: (value) => _clearError("name"),
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Policy Name", labelText: "Policy Name",
labelStyle: labelStyle: GoogleFonts.poppins(
GoogleFonts.poppins(fontSize: 12, color: Colors.grey), fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -747,8 +775,10 @@ class _PolicyState extends State<Policy> {
), ),
if (errorMessages["name"] != null) ...[ if (errorMessages["name"] != null) ...[
SizedBox(height: 5), SizedBox(height: 5),
Text(errorMessages["name"]!, Text(
style: GoogleFonts.poppins(color: Colors.red, fontSize: 10)), errorMessages["name"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
),
], ],
], ],
); );
@ -758,11 +788,14 @@ class _PolicyState extends State<Policy> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Policy Type", Text(
"Policy Type *",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF575A74))), color: Color(0xFF575A74),
),
),
SizedBox(height: 5), SizedBox(height: 5),
Row( Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -770,8 +803,10 @@ class _PolicyState extends State<Policy> {
), ),
if (errorMessages["trip_type"] != null) ...[ if (errorMessages["trip_type"] != null) ...[
SizedBox(height: 5), SizedBox(height: 5),
Text(errorMessages["trip_type"]!, Text(
style: GoogleFonts.poppins(color: Colors.red, fontSize: 10)), errorMessages["trip_type"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
),
], ],
], ],
); );
@ -786,8 +821,10 @@ class _PolicyState extends State<Policy> {
// color: Colors.blueGrey.shade200, // color: Colors.blueGrey.shade200,
width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: Colors.blueGrey.shade100, width: 0.35)), border: Border.all(color: Colors.blueGrey.shade100, width: 0.35),
child: isDesktop ),
child:
isDesktop
? Padding( ? Padding(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Column( child: Column(
@ -900,16 +937,13 @@ class _PolicyState extends State<Policy> {
color: Colors.grey.withOpacity(0.3), color: Colors.grey.withOpacity(0.3),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
) ),
], ],
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
name, name,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(color: Colors.black, fontSize: 12),
color: Colors.black,
fontSize: 12,
),
), ),
), ),
); );
@ -942,7 +976,7 @@ class _PolicyState extends State<Policy> {
color: Colors.grey.withOpacity(0.3), color: Colors.grey.withOpacity(0.3),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
) ),
], ],
), ),
alignment: Alignment.center, alignment: Alignment.center,
@ -967,10 +1001,10 @@ class _PolicyState extends State<Policy> {
padding: isDesktop ? const EdgeInsets.only(left: 30, top: 8) : null, padding: isDesktop ? const EdgeInsets.only(left: 30, top: 8) : null,
width: isDesktop ? MediaQuery.of(context).size.width * 0.62 : null, width: isDesktop ? MediaQuery.of(context).size.width * 0.62 : null,
// width: isDesktop ? MediaQuery.of(context).size.width * 0.75 : null, // width: isDesktop ? MediaQuery.of(context).size.width * 0.75 : null,
child: isDesktop child:
isDesktop
? Container( ? Container(
// color: Colors.amber, // color: Colors.amber,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [_buildPolicyServiceOrdering(isDesktop)], children: [_buildPolicyServiceOrdering(isDesktop)],
@ -992,8 +1026,9 @@ class _PolicyState extends State<Policy> {
} }
// Sort services by 'order' // Sort services by 'order'
ServicesChoosed! ServicesChoosed!.sort(
.sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); (a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0),
);
List<String> services = List<String> services =
ServicesChoosed!.map((service) => service['name'].toString()).toList(); ServicesChoosed!.map((service) => service['name'].toString()).toList();
@ -1003,15 +1038,22 @@ class _PolicyState extends State<Policy> {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Flex( child: Flex(
direction: Axis.horizontal, direction: Axis.horizontal,
children: services.asMap().entries.map((entry) { children:
services.asMap().entries.map((entry) {
int index = entry.key + 1; int index = entry.key + 1;
String service = entry.value; String service = entry.value;
bool isSelected = selectedServiceIndex.value == index.toString(); String serviceId = index.toString();
bool isSelected =
selectedServiceIndex.value == index.toString();
return SizedBox( return SizedBox(
// width: isDesktop ? 40 : null, // width: isDesktop ? 40 : null,
height: isDesktop height:
? max((MediaQuery.of(context).size.height * 0.075), 10) isDesktop
? max(
(MediaQuery.of(context).size.height * 0.075),
10,
)
: 45, : 45,
// max((MediaQuery.of(context).size.height * 0.09), 10) // max((MediaQuery.of(context).size.height * 0.09), 10)
@ -1022,12 +1064,21 @@ class _PolicyState extends State<Policy> {
selectedServiceIndex.value = index.toString(); selectedServiceIndex.value = index.toString();
selectedService = service; selectedService = service;
print(
" selectedServiceIndex.value - ${selectedServiceIndex.value}",
);
// policyCriteriaKey.currentState?.fieldForPolicy();
// policyCriteriaKey.currentState
// ?.addOrUpdatePolicy(selectedServiceIndex.value);
if (selectedService == "Flight" || if (selectedService == "Flight" ||
selectedService == "Train") { selectedService == "Train") {
showClass = true; showClass = true;
showCost = true; showCost = true;
int serviceCode = selectedService == "Flight" ? 1 : 2; int serviceCode = selectedService == "Flight" ? 1 : 2;
policyCriteriaKey.currentState?.fetchTrainFlightClass();
policyCriteriaKey.currentState
?.fetchTrainFlightClass();
} else if (selectedService == "Accommodation") { } else if (selectedService == "Accommodation") {
showClass = true; showClass = true;
showCost = false; showCost = false;
@ -1039,10 +1090,13 @@ class _PolicyState extends State<Policy> {
}, },
child: Container( child: Container(
margin: const EdgeInsets.all(5), margin: const EdgeInsets.all(5),
padding: isDesktop padding:
isDesktop
? const EdgeInsets.all(8) ? const EdgeInsets.all(8)
: const EdgeInsets.symmetric( : const EdgeInsets.symmetric(
horizontal: 8, vertical: 3), horizontal: 8,
vertical: 3,
),
alignment: Alignment.center, alignment: Alignment.center,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -1050,19 +1104,24 @@ class _PolicyState extends State<Policy> {
Text( Text(
service, service,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
color: isSelected color:
isSelected
? const Color(0xFF114D8B) ? const Color(0xFF114D8B)
: Colors.black87, : Colors.black87,
fontSize: 13, fontSize: 13,
fontWeight: fontWeight:
isSelected ? FontWeight.bold : FontWeight.w500, isSelected
decoration: TextDecoration ? FontWeight.bold
: FontWeight.w500,
decoration:
TextDecoration
.none, // remove built-in underline .none, // remove built-in underline
), ),
), ),
if (isSelected) if (isSelected)
const SizedBox( const SizedBox(
height: 1), // spacing between text and underline height: 1,
), // spacing between text and underline
if (isSelected) if (isSelected)
Container( Container(
height: 2, height: 2,
@ -1072,7 +1131,8 @@ class _PolicyState extends State<Policy> {
], ],
), ),
), ),
)); ),
);
}).toList(), }).toList(),
), ),
), ),
@ -1104,15 +1164,21 @@ class _PolicyState extends State<Policy> {
scrollDirection: isDesktop ? Axis.vertical : Axis.horizontal, scrollDirection: isDesktop ? Axis.vertical : Axis.horizontal,
child: Flex( child: Flex(
direction: isDesktop ? Axis.vertical : Axis.horizontal, direction: isDesktop ? Axis.vertical : Axis.horizontal,
children: services.asMap().entries.map((entry) { children:
services.asMap().entries.map((entry) {
int index = entry.key + 1; int index = entry.key + 1;
String service = entry.value; String service = entry.value;
bool isSelected = selectedServiceIndex.value == index.toString(); bool isSelected =
selectedServiceIndex.value == index.toString();
return SizedBox( return SizedBox(
width: isDesktop ? 180 : null, width: isDesktop ? 180 : null,
height: isDesktop height:
? max((MediaQuery.of(context).size.height * 0.075), 10) isDesktop
? max(
(MediaQuery.of(context).size.height * 0.075),
10,
)
: 45, : 45,
// max((MediaQuery.of(context).size.height * 0.09), 10) // max((MediaQuery.of(context).size.height * 0.09), 10)
@ -1140,9 +1206,15 @@ class _PolicyState extends State<Policy> {
}, },
child: Container( child: Container(
margin: EdgeInsets.all(5), margin: EdgeInsets.all(5),
padding: isDesktop padding:
isDesktop
? EdgeInsets.all(8) ? EdgeInsets.all(8)
: EdgeInsets.only(top: 3, bottom: 3, left: 8, right: 8), : EdgeInsets.only(
top: 3,
bottom: 3,
left: 8,
right: 8,
),
decoration: BoxDecoration( decoration: BoxDecoration(
// color: Colors.blue, // color: Colors.blue,
color: isSelected ? Color(0xFF114D8B) : Colors.white, color: isSelected ? Color(0xFF114D8B) : Colors.white,
@ -1168,10 +1240,12 @@ class _PolicyState extends State<Policy> {
color: isSelected ? Colors.white : Colors.black87, color: isSelected ? Colors.white : Colors.black87,
fontSize: 13, fontSize: 13,
fontWeight: fontWeight:
isSelected ? FontWeight.bold : FontWeight.w100), isSelected ? FontWeight.bold : FontWeight.w100,
), ),
), ),
)); ),
),
);
}).toList(), }).toList(),
), ),
), ),
@ -1202,7 +1276,8 @@ class _PolicyState extends State<Policy> {
}); });
}); });
}, },
)); ),
);
} }
List<Widget> _buildTripType(bool isDesktop) { List<Widget> _buildTripType(bool isDesktop) {
@ -1224,7 +1299,8 @@ class _PolicyState extends State<Policy> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
color: _selectedTripType == "1" ? Colors.white : Colors.black, color: _selectedTripType == "1" ? Colors.white : Colors.black,
fontWeight: _selectedTripType == "1" ? FontWeight.w600 : null, fontWeight: _selectedTripType == "1" ? FontWeight.w600 : null,
fontSize: 13), fontSize: 13,
),
), ),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
@ -1251,11 +1327,12 @@ class _PolicyState extends State<Policy> {
width: _selectedTripType == "1" ? 2 : 1, width: _selectedTripType == "1" ? 2 : 1,
), ),
), ),
child: _selectedTripType == "1" child:
_selectedTripType == "1"
? Icon(Icons.rectangle, size: 8, color: Colors.white) ? Icon(Icons.rectangle, size: 8, color: Colors.white)
: null, // Add checkmark if selected : null, // Add checkmark if selected
), ),
) ),
], ],
), ),
), ),
@ -1306,11 +1383,12 @@ class _PolicyState extends State<Policy> {
width: _selectedTripType == "2" ? 2 : 1, width: _selectedTripType == "2" ? 2 : 1,
), ),
), ),
child: _selectedTripType == "2" child:
_selectedTripType == "2"
? Icon(Icons.rectangle, size: 8, color: Colors.white) ? Icon(Icons.rectangle, size: 8, color: Colors.white)
: null, // Add checkmark if selected : null, // Add checkmark if selected
), ),
) ),
], ],
), ),
@ -1348,15 +1426,12 @@ class _PolicyState extends State<Policy> {
onPressed: () { onPressed: () {
context.go('/PolicyList'); context.go('/PolicyList');
}, },
child: Text( child: Text("Cancel", style: GoogleFonts.poppins(fontSize: 10)),
"Cancel",
style: GoogleFonts.poppins(fontSize: 10),
)),
SizedBox(
width: 20,
), ),
SizedBox(width: 20),
MouseRegion( MouseRegion(
cursor: isViewMode cursor:
isViewMode
? SystemMouseCursors.forbidden ? SystemMouseCursors.forbidden
: SystemMouseCursors.click, : SystemMouseCursors.click,
child: ElevatedButton( child: ElevatedButton(
@ -1376,12 +1451,9 @@ class _PolicyState extends State<Policy> {
), ),
onPressed: onPressed:
isViewMode ? null : handleSubmit, // Disable when in view mode isViewMode ? null : handleSubmit, // Disable when in view mode
child: Text( child: Text("Submit", style: GoogleFonts.poppins(fontSize: 10)),
"Submit",
style: GoogleFonts.poppins(fontSize: 10),
), ),
), ),
)
]; ];
} }
@ -1402,10 +1474,7 @@ class _PolicyState extends State<Policy> {
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
dense: true, dense: true,
title: Text( title: Text("Domestic", style: GoogleFonts.poppins(fontSize: 12)),
"Domestic",
style: GoogleFonts.poppins(fontSize: 12),
),
value: "1", value: "1",
groupValue: _selectedTripType, groupValue: _selectedTripType,
onChanged: (value) { onChanged: (value) {

View File

@ -87,6 +87,7 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
void initState() { void initState() {
super.initState(); super.initState();
fieldForPolicy(); fieldForPolicy();
widget.selectedTabNotifier.addListener(() { widget.selectedTabNotifier.addListener(() {
print("selectedTab changed: ${widget.selectedTabNotifier.value}"); print("selectedTab changed: ${widget.selectedTabNotifier.value}");
fieldForPolicy(); fieldForPolicy();
@ -99,11 +100,57 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
userId = await getUserId(); userId = await getUserId();
} }
void saveCurrentPolicy() { // void saveCurrentPolicy(List<Map<String, dynamic>> services) {
// print("saveCurrentPolicy- $services");
// if (ServiceId != null) {
// print("Saving curremt Add or Update");
// addOrUpdatePolicy(ServiceId!);
// }
// }
void saveCurrentPolicy(List<Map<String, dynamic>> services) {
print("saveCurrentPolicy- $services");
// Step 1: Save currently selected policy first (if not already saved)
if (ServiceId != null) { if (ServiceId != null) {
print("Saving curremt Add or Update"); print("Saving current Add or Update");
addOrUpdatePolicy(ServiceId!); addOrUpdatePolicy(ServiceId!);
} }
// Step 2: Collect existing service_ids from policyData
final existingServiceIds =
policyData?.map((e) => e['service_id'].toString()).toSet();
// Step 3: Loop through all service definitions
for (var service in services) {
String id = service['service_id'].toString();
// Skip if already present
if (existingServiceIds!.contains(id)) continue;
// Step 4: Initialize any missing controllers or data
costController.putIfAbsent(id, () => TextEditingController());
classAction.putIfAbsent(id, () => "1");
FirstApproverAction.putIfAbsent(id, () => "None");
SecondApproverAction.putIfAbsent(id, () => "None");
ThirdApproverAction.putIfAbsent(id, () => "None");
SelectedParallelProcess.putIfAbsent(id, () => "3");
// Step 5: Add default policy entry
policyData?.add({
"service_id": int.parse(id),
"cost": "",
"class": classAction[id],
"a1_action": FirstApproverAction[id],
"a2_action": SecondApproverAction[id],
"a3_action": ThirdApproverAction[id],
"parallel_process_from": SelectedParallelProcess[id],
"created_by": widget.userId,
});
}
// Step 6: Emit updated policy data
widget.onPolicyDataChanged(policyData);
} }
// To set the data (update) // To set the data (update)
@ -148,6 +195,7 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
} }
void addOrUpdatePolicy(String serviceId) { void addOrUpdatePolicy(String serviceId) {
print("addOrUpdatePolicyserviceId - $serviceId");
// 1. First, find existing item if any // 1. First, find existing item if any
final existingIndex = final existingIndex =
policyData!.indexWhere((item) => item["service_id"] == serviceId); policyData!.indexWhere((item) => item["service_id"] == serviceId);
@ -297,6 +345,7 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
// Save current input to policyData before switching // Save current input to policyData before switching
if (ServiceId != null) { if (ServiceId != null) {
print("Calling addOrUpdatePolicy");
addOrUpdatePolicy( addOrUpdatePolicy(
ServiceId!); // 👈 Save current values for existing service ServiceId!); // 👈 Save current values for existing service
} }
@ -304,12 +353,12 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
ServiceId = widget.selectedTabNotifier.value ?? "1"; ServiceId = widget.selectedTabNotifier.value ?? "1";
// Initialize controllers and variables if not present // Initialize controllers and variables if not present
costController.putIfAbsent(ServiceId!, () => TextEditingController()); costController.putIfAbsent(ServiceId!, () => TextEditingController());
classAction.putIfAbsent(ServiceId!, () => null); classAction.putIfAbsent(ServiceId!, () => "1");
// classController.putIfAbsent(ServiceId!, () => TextEditingController()); // classController.putIfAbsent(ServiceId!, () => TextEditingController());
FirstApproverAction.putIfAbsent(ServiceId!, () => null); FirstApproverAction.putIfAbsent(ServiceId!, () => "None");
SecondApproverAction.putIfAbsent(ServiceId!, () => null); SecondApproverAction.putIfAbsent(ServiceId!, () => "None");
ThirdApproverAction.putIfAbsent(ServiceId!, () => null); ThirdApproverAction.putIfAbsent(ServiceId!, () => "None");
SelectedParallelProcess.putIfAbsent(ServiceId!, () => "3"); SelectedParallelProcess.putIfAbsent(ServiceId!, () => "3");
}); });

View File

@ -0,0 +1,428 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:frontend/Screens/group/group.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:responsive_builder/responsive_builder.dart';
import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
class PolicyListBackup extends StatefulWidget {
@override
_PolicyListBackupState createState() => _PolicyListBackupState();
}
class _PolicyListBackupState extends State<PolicyListBackup> {
final ApiService apiService = ApiService();
List<dynamic>? apiAllGroups;
Color? layoutColor;
Color? bodyColor;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
loadAllGroups();
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;
});
}
Future<void> loadAllGroups() async {
try {
final result = await apiService.fetchAllPolicy();
// Sort by policy_id descending (latest first)
result.sort((a, b) {
int idA = int.tryParse(a['policy_id'].toString()) ?? 0;
int idB = int.tryParse(b['policy_id'].toString()) ?? 0;
return idB.compareTo(idA); // latest first
});
setState(() {
apiAllGroups = result;
});
print("Fetched services: $apiAllGroups");
} catch (e) {
print('Error fetching role list: $e');
}
}
void handleActiveStatus(
Map<String, dynamic> policyData,
String policyId,
String currentStatus,
) async {
print("Toggling user status - $policyId (Current: $currentStatus)");
final String apiUrlData =
'$apiUrl/api/policy/createOrUpdate'; // API for updating user
final String? token = await getToken();
if (token == null) {
print("Error: Token not found");
return;
}
// Toggle status: If active ("1"), set to inactive ("0"); otherwise, activate ("1")
String newStatus = (currentStatus == "1") ? "0" : "1";
print("STatus 1 - $newStatus");
final int? selectedPolicyId;
if (policyId.isNotEmpty) {
selectedPolicyId = int.tryParse(policyId);
policyData['policy_id'] = selectedPolicyId; // Add only if updating
policyData['is_active'] = newStatus; // Add only if updating
}
try {
final response = await http.post(
Uri.parse(apiUrlData),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode(policyData), // Convert map to JSON
);
if (response.statusCode == 200) {
print("policyData submitted successfully!");
print("Response: ${response.body}");
loadAllGroups();
} else {
print("Failed to submit policyData. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting policyData: $e");
}
}
void deletePolicy(Map<String, dynamic> policydata, policyId, status) {
print("policyId : $policyId");
print("policystatus: $status");
print("policysData: $policydata");
// handleActiveStatus(groupdata, groupId, status);
print("Calling handleActiveStatus with: id=$policyId, status=$status");
handleActiveStatus(policydata, policyId.toString(), status.toString());
}
// Future<void> deleteGroupFromApi(int groupId) async {
// try {
// await apiService.deleteGroup(groupId); // your delete API call
// deleteGroup(groupId); // remove from UI list
// } catch (e) {
// print('Error deleting group: $e');
// }
// }'
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: Color(0xFFf5f5f5),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding:
isDesktop
? EdgeInsets.symmetric(
horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildGroupList(isDesktop)),
],
),
),
);
},
);
}
Widget buildGroupList(bool isDesktop) {
return Container(
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
padding: const EdgeInsets.only(left: 10, right: 10, top: 8),
decoration: BoxDecoration(
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
// decoration: BoxDecoration(
// // color: Colors.amber,
// color: Color(0xFFE1F5FE),
// // color: bodyColor,
// border: Border.all(
// // color: Color(0xFFF7F7FB),
// color: Colors.white,
// width: 3.5)),
child: buildGroupListLayout(isDesktop),
);
}
Widget buildGroupListLayout(bool isDesktop) {
return 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,
// ),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
'Policy List',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
IconButton(
icon: const Icon(Icons.keyboard_arrow_down),
onPressed: () {},
),
],
),
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: , width: 1),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () async {
// List<dynamic> users = await futureUsers;
context.go('/Policy');
},
child: Row(
children: [
Text(
'New Policy',
style: GoogleFonts.poppins(fontSize: 12),
),
SizedBox(width: 5),
Icon(Icons.add_circle_outline_rounded, color: Colors.white),
],
),
),
],
),
SizedBox(height: 5),
Row(
children: [
Expanded(
child: Container(
height: MediaQuery.of(context).size.height * 0.8,
padding: const EdgeInsets.all(10),
// margin: const EdgeInsets.only(bottom: 10),
color: Colors.white,
// color: Colors.red.shade100,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(children: [buildGroupListView(isDesktop)]),
),
),
),
],
),
],
),
);
}
// Widget buildGroupListView(bool isDesktop) {
// return Container(
// child: Text("DAta"),
// );
// }
Widget buildGroupListView(bool isDesktop) {
if (apiAllGroups == null || apiAllGroups!.isEmpty) {
return Center(child: Text("No Policy Found."));
}
return ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: apiAllGroups!.length,
itemBuilder: (context, index) {
final policy = apiAllGroups![index];
return Card(
// color: bodyColor,
// color: Color(0xFFF5F5F5),
color: Colors.white,
margin: EdgeInsets.symmetric(vertical: 6, horizontal: 10),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
flex: 1,
child: Text(
"Policy Name",
style: GoogleFonts.poppins(
fontSize: 11.5,
fontWeight: FontWeight.w400,
),
),
),
Expanded(
flex: 1,
child: Text(
"Policy Type",
style: GoogleFonts.poppins(
fontSize: 11.5,
fontWeight: FontWeight.w400,
),
),
),
// Expanded(flex: 1, child: Text("${policy['created_by']}")),
],
),
SizedBox(height: 4),
Row(
children: [
Expanded(
flex: 1,
child: Text(
"${policy['name']}",
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
Expanded(
flex: 1,
child: Text(
policy['domestic'] == "1"
? "Domestic"
: "International",
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
// Expanded(flex: 1, child: Text("${policy['created_by']}")),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () async {
final rawId = policy['policy_id'];
final intPolicyId =
rawId is int
? rawId
: int.tryParse(rawId.toString()) ?? 0;
Map<String, dynamic> policyData = await apiService
.getSinglePolicy(intPolicyId);
print("PolicyDATa: $policyData");
context.go("/Policy", extra: policyData);
},
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
),
SizedBox(width: 5),
GestureDetector(
onTap: () {
final idStr = policy['policy_id'];
final id = int.tryParse(idStr.toString());
if (id == null) {
print("group_id is null");
return;
}
final status = policy['is_active'];
deletePolicy(policy, id, status);
},
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15,
),
),
],
),
],
),
),
);
},
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,542 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import '../../config/apiUrl.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_forex.dart';
import 'travellerList.dart';
class TravellerData extends StatefulWidget {
final Future<List<dynamic>> Function() fetchGetTraveller;
final bool isDesktop;
final Color? layoutColor;
final int? travellerId; // <-- Add this
final Map<String, dynamic>? travellerData;
const TravellerData({
super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetTraveller,
this.travellerId,
this.travellerData,
});
@override
TravellerDataState createState() => TravellerDataState();
}
class TravellerDataState extends State<TravellerData> {
final ApiService apiService = ApiService();
Map<String, dynamic>? apiData;
final Map<String, FocusNode> focusNodes = {
"name": FocusNode(),
"description": FocusNode(),
};
final Map<String, TextEditingController> controllers = {};
Map<String, String> errorMessages = {};
String? selectedName;
String? selectedDescription;
String? userId;
int? travellerDataId;
late String isActive = "1";
List<String> dataHeader = ["first_name", "last_name", "email", "mobile"];
Map<String, dynamic> travellerDetails() {
final data = {
// "traveller_id": int.parse(travellerId),
"first_name": controllers["first_name"]?.text,
"last_name": controllers["last_name"]?.text,
"email": controllers["email"]?.text,
"mobile": controllers["mobile"]?.text,
"is_active": isActive,
};
return data;
}
@override
void initState() {
super.initState();
apiData = null;
for (var field in dataHeader) {
controllers[field] = TextEditingController();
}
if (widget.travellerId != null) {
print('Editing D ID: ${widget.travellerId}');
updateTravellerDetails();
}
}
void _clearError() {
setState(() {
errorMessages.clear();
});
}
@override
void dispose() {
for (var controller in controllers.values) {
controller.dispose();
}
super.dispose();
}
void updateTravellerDetails() {
print("Inside Update Function - ${widget.travellerData}");
final data = widget.travellerData;
if (data == null) return;
setState(() {
controllers['first_name']?.text = data['first_name'] ?? '';
controllers['last_name']?.text = data['last_name'] ?? '';
controllers['email']?.text = data['email'].toString();
controllers['mobile']?.text = data['mobile'].toString();
isActive = data["is_active"];
final travellerId = int.tryParse(data['traveller_id'].toString());
travellerDataId = travellerId;
});
}
void toggleStatus() {
setState(() {
isActive = isActive == "1" ? "0" : "1";
});
}
bool validateData() {
errorMessages.clear();
final data = {
"first_name": controllers["first_name"]?.text,
"last_name": controllers["last_name"]?.text,
"email": controllers["email"]?.text,
"mobile": controllers["mobile"]?.text,
};
final requiredFields = ["first_name", "last_name", "email", "mobile"];
bool hasFocused = false;
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field]!.trim().isEmpty) {
errorMessages[field] = "Required";
if (!hasFocused) {
focusNodes[field]?.requestFocus();
hasFocused = true;
}
}
}
if (data["mobile"] != null && data["mobile"].toString().isNotEmpty) {
if (!RegExp(r"^\d{10}$").hasMatch(data["mobile"].toString())) {
errorMessages["mobile"] =
"Enter 10 digits"; // Invalid mobile number format
}
}
if (data["email"] != null && data["email"].toString().isNotEmpty) {
if (!RegExp(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
).hasMatch(data["email"].toString())) {
errorMessages["email"] = "Invalid email format"; // Invalid email format
}
}
return errorMessages.isEmpty;
}
Future<void> handleSubmit() async {
userId = await getUserId();
setState(() {
// This triggers UI rebuild with error messages
if (validateData()) {
postTravellerData();
}
});
final travellerData1 = travellerDetails();
print("submit data - $travellerData1");
}
Future<void> postTravellerData({int isActive = 1}) async {
// final remarksData = getData();
final travellerData = travellerDetails();
print("initially value of the Traveller - $travellerData");
// static here
final orgId = await getOrgId();
final String apiUrldata;
travellerData["org_id"] = orgId;
if (travellerDataId != null) {
print("for edit traveller id - $travellerDataId");
apiUrldata = '$apiUrl/api/travellers/update/$travellerDataId';
travellerData["traveller_id"] = travellerDataId.toString();
travellerData["updated_by"] = userId;
(travellerData.containsKey("created_by"))
? travellerData.remove("created_by")
: '';
} else {
print("for add Traveller id - null");
apiUrldata = '$apiUrl/api/travellers/create';
print("called apiUrl - $apiUrldata");
travellerData["created_by"] = userId;
}
print("recently Traveller data - $travellerData");
final token = await getToken(); // Fetch token
if (token == null) {
throw Exception('Token not found. Please log in.');
}
try {
final uri = Uri.parse(apiUrldata);
final headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
final body = jsonEncode(travellerData);
final response =
travellerDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
switch (response.statusCode) {
case 200:
print("Update - Response: ${response.body}");
_clearError();
widget.fetchGetTraveller();
Navigator.of(context).pop();
break;
case 201:
print("Save - Response: ${response.body}");
_clearError();
await widget.fetchGetTraveller();
Navigator.of(context).pop();
break;
default:
print("Failed to submit traveller. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting plan: $e");
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
// contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
content: SizedBox(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Row 1: Title + Edit + Delete buttons
Row(
children: [
Text(
(travellerDataId != null)
? 'Edit Traveller'
: 'Create Traveller',
style: GoogleFonts.poppins(
fontSize: 15,
color: Colors.black,
),
),
const Spacer(),
],
),
const SizedBox(height: 2),
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
const SizedBox(height: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"First Name *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["first_name"],
focusNode: focusNodes["first_name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "First Name",
labelStyle: TextStyle(
fontSize: 11,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["first_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["first_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Last Name *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["last_name"],
focusNode: focusNodes["last_name"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Last Name",
labelStyle: TextStyle(
fontSize: 11,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["last_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["last_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Email *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["email"],
focusNode: focusNodes["email"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Email",
labelStyle: TextStyle(
fontSize: 11,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["email"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["email"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Mobile *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["mobile"],
focusNode: focusNodes["mobile"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Mobile",
labelStyle: TextStyle(
fontSize: 11,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["mobile"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["mobile"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
SizedBox(height: 15),
if (travellerDataId != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Change Status ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
Tooltip(
message:
isActive == "1"
? "Tap to deactivate"
: "Tap to activate",
child: GestureDetector(
onTap: toggleStatus,
child: Text(
isActive == "1" ? "Active" : "Inactive",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
color: isActive == "1" ? Colors.green : Colors.red,
),
),
),
),
],
),
if (travellerDataId != null) SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// SizedBox(
// child: ElevatedButton(
// onPressed: () {
// // You can get text from commentController.text
// Navigator.of(context).pop(); // Close the modal
// },
// style: ElevatedButton.styleFrom(
// backgroundColor: widget.layoutColor,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// ),
// ),
// child: Text('Cancel',
// style: GoogleFonts.poppins(
// fontSize: 13, color: Colors.white)),
// ),
// ),
// SizedBox(
// width: 10,
// ),
SizedBox(
child: ElevatedButton(
onPressed: () {
handleSubmit();
// You can get text from commentController.text
// Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
backgroundColor: widget.layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
'Save',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.white,
),
),
),
),
],
),
// : SizedBox.shrink(),
],
),
),
),
);
}
}

View File

@ -0,0 +1,848 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../utils/pagination.dart';
import 'travellerDetails.dart';
class TravellerList extends StatefulWidget {
const TravellerList({super.key});
@override
TravellerListState createState() => TravellerListState();
}
class TravellerListState extends State<TravellerList> {
final GlobalKey<TravellerListState> travellerListKey =
GlobalKey<TravellerListState>();
final ApiService apiService = ApiService();
late Future<List<dynamic>> futureTraveller;
late Map<String, dynamic> depSingleData;
String? selectedTravellerId;
String? orgId;
Color? layoutColor;
Color? bodyColor;
List allTraveller = [];
List filteredTraveller = [];
TextEditingController searchController = TextEditingController();
int currentPage = 0;
int itemsPerPage = 10;
@override
void initState() {
super.initState();
futureTraveller = fetchGetTraveller();
futureTraveller.then((object) {
setState(() {
allTraveller = object;
});
});
WidgetsBinding.instance.addPostFrameCallback((_) {
loadInitialData();
});
// futurePlans = fetchPlans();
}
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;
});
}
Future<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('auth_token');
}
Future<List<dynamic>> refreshData() {
print("Calling Refresh Data");
futureTraveller = fetchGetTraveller();
return futureTraveller.then((object) {
print("Calling Refresh Data $object");
setState(() {
allTraveller = object;
});
return object;
});
}
Future<List<dynamic>> fetchGetTraveller() async {
String? ordId = await getOrgId();
final String apiUrlData =
'$apiUrl/api/travellers?for=table_view&org_id=$ordId';
final String? token = await getToken();
print("Fetch Traveller");
print("2KN Here : $token");
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',
},
);
print("called api : $apiUrlData");
if (response.statusCode == 200) {
final data = json.decode(response.body);
return data['data']; // Returning raw JSON list
} else {
throw Exception('Failed to load users');
}
}
void filterTraveller(String query) {
// print("all before filtering: $query");
// final lowerQuery = query.toLowerCase();
// setState(() {
// filteredTraveller = allTraveller.where((object) {
// return (object['traveller_id']?.toLowerCase().contains(lowerQuery) ??
// false) ||
// (object['description']?.toLowerCase().contains(lowerQuery) ?? false) ||
// (object['user']?.toLowerCase().contains(lowerQuery) ?? false) ||
// (object['is_active']?.toLowerCase().contains(lowerQuery) ?? false);
// }).toList();
// });
// print("filteredPlans: $filteredTraveller");
print("all before filtering: $query");
final lowerQuery = query.toLowerCase();
setState(() {
filteredTraveller =
allTraveller.where((object) {
final isActiveStatus =
object['is_active'] == "1" ? "active" : "inactive";
return (object['traveller_id']?.toLowerCase().contains(
lowerQuery,
) ??
false) ||
(object['first_name']?.toLowerCase().contains(lowerQuery) ??
false) ||
(object['last_name']?.toLowerCase().contains(lowerQuery) ??
false) ||
(object['mobile']?.toLowerCase().contains(lowerQuery) ??
false) ||
(object['email']?.toLowerCase().contains(lowerQuery) ??
false) ||
(isActiveStatus.contains(lowerQuery));
}).toList();
currentPage = 0;
});
print("filteredTraveller: $filteredTraveller");
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: Color(0xFFf5f5f5),
// appBar: isDesktop ? null : const CustomAppBar(title: 'User Management'),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
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),
// const Expanded(child: Center(child: Text("User Page Content"))),
Expanded(child: buildGroupList(isDesktop)),
],
),
),
);
},
);
}
Widget buildGroupList(bool isDesktop) {
return Container(
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
// decoration: BoxDecoration(
// // color: Colors.amber,
// // color: bodyColor,
// color: Color(0xFFE1F5FE),
// border: Border.all(
// color: Colors.white,
// // color: Color(0xFFF7F7FB),
// width: 3.5)),
child: buildUserTable(isDesktop),
);
}
Widget buildUserTable(bool isDesktop) {
return 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,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
color: Colors.white,
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Divider(
// thickness: 0.2, // how "thick" the line is
// color: Colors.grey, // optional
// ),
Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
'Traveller Details',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
],
),
if (isDesktop)
SizedBox(width: MediaQuery.of(context).size.width * 0.16),
if (isDesktop)
Container(
width: MediaQuery.of(context).size.width * 0.2,
height: 40,
child: TextField(
controller: searchController,
onChanged: filterTraveller,
decoration: InputDecoration(
hintText: "Search ...",
hintStyle: TextStyle(
fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon(
Icons.search,
color: Color(0xFF9E9DBD),
size: 18,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade200,
width: 0.5,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
),
),
style: GoogleFonts.poppins(fontSize: 12),
),
),
// SizedBox(width: 16),
Spacer(),
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: EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
),
),
onPressed: () async {
showDialog(
context: context,
builder:
(context) => TravellerData(
isDesktop: isDesktop,
layoutColor: layoutColor!,
fetchGetTraveller: refreshData,
// role:
// "Travel Agent"
),
);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add Traveller",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
],
),
if (!isDesktop) SizedBox(height: 5),
isDesktop
? SizedBox.shrink()
: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.8,
height: 35,
child: TextField(
controller: searchController,
onChanged: filterTraveller,
decoration: InputDecoration(
hintText: "Search ...",
hintStyle: TextStyle(
fontSize: 12,
color: Color(0xFF9E9DBD),
),
prefixIcon: Icon(
Icons.search,
color: Color(0xFF9E9DBD),
size: 18,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade200,
width: 0.5,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
),
),
style: GoogleFonts.poppins(fontSize: 12),
),
),
// SizedBox(width: 16),
],
),
const SizedBox(height: 10),
FutureBuilder<List<dynamic>>(
future: futureTraveller,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError ||
!snapshot.hasData ||
snapshot.data!.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 8),
Text(
"No Traveller Available ",
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
const SizedBox(height: 20),
Text(
"Please Create Traveller Details",
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 16,
color: Colors.grey,
),
),
const SizedBox(height: 20),
],
),
),
);
}
/* Here collect the list to displayed the data in table or card Used */
List<dynamic> object =
filteredTraveller.isNotEmpty
? filteredTraveller
: allTraveller;
/* List is Sorting here */
object.sort((a, b) {
DateTime dateA = DateTime.parse(a['created_on']);
DateTime dateB = DateTime.parse(b['created_on']);
return dateB.compareTo(dateA); // Descending: newest first
});
/* For pagination for list ... */
List paginatedTraveller =
object
.skip(currentPage * itemsPerPage)
.take(itemsPerPage)
.toList();
/* Table ... */
Widget table = LayoutBuilder(
builder: (context, constraints) {
double minWidth = isDesktop ? constraints.maxWidth : 1300;
return ConstrainedBox(
constraints: BoxConstraints(minWidth: minWidth),
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(
label: Text(
'Name',
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
DataColumn(
label: Text(
'Email',
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
DataColumn(
label: Text(
'Mobile',
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
DataColumn(
label: Text(
'Status',
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
DataColumn(
label: Text(
'Actions',
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
],
rows:
paginatedTraveller.map((tableObject) {
String fullName =
'${tableObject['first_name'] ?? ''} ${tableObject['last_name'] ?? ''}';
String travellerId =
tableObject['traveller_id']
.toString(); // Get user ID
bool isSelected =
selectedTravellerId == travellerId;
return DataRow(
cells: [
DataCell(
Text(
fullName ?? 'N/A',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
),
),
),
DataCell(
Text(
tableObject['email'] ?? 'N/A',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
),
softWrap: true,
overflow: TextOverflow.ellipsis,
),
),
DataCell(
Text(
tableObject['mobile'] ?? 'N/A',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
),
softWrap: true,
overflow: TextOverflow.ellipsis,
),
),
DataCell(
Text(
tableObject['is_active'] == "1"
? 'Active'
: 'Inactive',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
color:
tableObject['is_active'] == "1"
? Colors.green
: Colors.grey,
),
softWrap: true,
overflow: TextOverflow.ellipsis,
),
),
DataCell(
GestureDetector(
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
onTap: () async {
// final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId);
//
final travellerId = int.tryParse(
tableObject['traveller_id']
.toString(),
);
if (travellerId != null) {
print(
"Table cell - traveller Id -- $travellerId",
);
final data = await apiService
.getTravellerDetailsFind(
travellerId,
);
print("TravellerId -- $data");
showDialog(
context: context,
builder:
(context) => TravellerData(
isDesktop: isDesktop,
travellerId:
travellerId, // Pass the ID
travellerData: data,
layoutColor: layoutColor!,
fetchGetTraveller:
refreshData,
// role:
// "Travel Agent"
),
);
} else {
print("Invalid ID");
}
},
),
),
],
);
}).toList(),
),
);
},
);
/* Card ... */
Widget buildMobileCardView(List<dynamic> paginatedUser) {
return ListView.builder(
itemCount: paginatedUser.length,
itemBuilder: (context, index) {
final cardObject = paginatedUser[index];
String fullName =
'${cardObject['first_name'] ?? ''} ${cardObject['last_name'] ?? ''}';
return Card(
color: Colors.white,
margin: EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 3,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Status and Employee Code
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
fullName ?? 'N/A',
style: GoogleFonts.poppins(
fontSize: 10,
color: Colors.black87,
fontWeight: FontWeight.w700,
),
),
GestureDetector(
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15,
),
onTap: () async {
// final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId);
//
final travellerId = int.tryParse(
cardObject['traveller_id'].toString(),
);
if (travellerId != null) {
print("travellerId -- $travellerId");
final data = await apiService
.getTravellerDetailsFind(
travellerId,
);
print("TravellerId -- $data");
showDialog(
context: context,
builder:
(context) => TravellerData(
isDesktop: isDesktop,
travellerId:
travellerId, // Pass the ID
travellerData: data,
layoutColor: layoutColor!,
// fetchGetTraveller: fetchGetTraveller,
fetchGetTraveller:
refreshData,
// role:
// "Travel Agent"
),
);
} else {
print("Invalid ID");
}
},
),
],
),
SizedBox(height: 2),
// Trip Id and Trip Name
// Name
Row(
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'${cardObject['email'] ?? 'N/A'}',
style: GoogleFonts.poppins(
fontSize: 10,
color: Colors.black87,
),
),
],
),
SizedBox(width: 10),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'${cardObject['mobile'] ?? 'N/A'}',
style: GoogleFonts.poppins(
fontSize: 10,
color: Colors.black87,
),
),
],
),
],
),
// Actions
// Actions
],
),
),
);
},
);
}
return Expanded(
child: Column(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child:
isDesktop
? (searchController.text.isNotEmpty &&
filteredTraveller.isEmpty
? Center(
child: Text(
"No matches found",
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey,
),
),
)
: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
))
: (searchController.text.isNotEmpty &&
filteredTraveller.isEmpty
? Center(
child: Text(
"No matches found",
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey,
),
),
)
: buildMobileCardView(
paginatedTraveller,
)),
),
// Expanded(
// child: isDesktop
// ? SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: table, // <-- your existing table
// )
// : buildMobileCardView(paginatedTraveller),
// ),
PaginationControls(
currentPage: currentPage,
itemsPerPage: itemsPerPage,
totalItems: object.length,
activeColor: layoutColor, // your theme color
onPageChanged: (page) {
setState(() {
currentPage = page;
});
},
onItemsPerPageChanged: (items) {
setState(() {
itemsPerPage = items;
currentPage = 0;
});
},
),
],
),
);
},
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,383 @@
import 'dart:convert';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import '../../../config/apiUrl.dart';
import '../../../services/apiService.dart';
import '../../../utils/auth_utils.dart';
import '../../../widgets/custom_user_form.dart';
class ChangePasswordDialogData extends StatefulWidget {
final dynamic isDesktop;
final dynamic layoutColor;
final dynamic updaterUserId;
final dynamic updaterEmail;
const ChangePasswordDialogData({
super.key,
this.isDesktop,
this.layoutColor,
this.updaterUserId,
this.updaterEmail
});
@override
ChangePasswordDialogDataState createState() => ChangePasswordDialogDataState();
}
class ChangePasswordDialogDataState extends State<ChangePasswordDialogData> {
final ApiService apiService = ApiService();
final Map<String, TextEditingController> controllers = {};
Map<String, String> errorMessages = {};
String? loggeduserId;
String? updaterUserIdForAPI;
List<String> dataHeader = [
"email",
"changePassword",
"confirmPassword"
];
// @override
// void initState() {
// super.initState();
//
// for (var field in dataHeader) {
// controllers[field] = TextEditingController();
// }
//
// setState(() {
// controllers['email']?.text = widget.updaterEmail ?? '';
// controllers['changePassword']?.text = '';
// controllers['confirmPassword']?.text = '';
// });
// }
@override
void initState() {
super.initState();
print("widget.updaterEmail: ${widget.updaterEmail}");
for (var field in dataHeader) {
controllers[field] = TextEditingController();
}
setState(() {
controllers['email']?.text = widget.updaterEmail ;
controllers['changePassword']?.text = '';
controllers['confirmPassword']?.text = '';
updaterUserIdForAPI = widget.updaterUserId;
});
}
void _clearError() {
setState(() {
errorMessages.clear();
});
}
@override
void dispose() {
for (var controller in controllers.values) {
controller.dispose();
}
super.dispose();
}
bool validateData() {
errorMessages.clear();
final String? email = controllers["email"]?.text;
final String? changePassword = controllers["changePassword"]?.text;
final String? confirmPassword = controllers["confirmPassword"]?.text;
// Required fields check
if (email == null || email.trim().isEmpty) {
errorMessages["email"] = "Required";
}
if (changePassword == null || changePassword.trim().isEmpty) {
errorMessages["changePassword"] = "Required";
}
if (confirmPassword == null || confirmPassword.trim().isEmpty) {
errorMessages["confirmPassword"] = "Required";
}
// Password match check
if ((changePassword?.isNotEmpty ?? false) &&
(confirmPassword?.isNotEmpty ?? false) &&
changePassword != confirmPassword) {
errorMessages["changePassword"] = "Passwords do not match";
errorMessages["confirmPassword"] = "Passwords do not match";
}
// setState(() {}); // Update UI with any error messages
return errorMessages.isEmpty;
}
Future<void> handleSubmit() async {
loggeduserId = await getUserId();
setState(() {
// This triggers UI rebuild with error messages
if (validateData()) {
postData();
}
});
}
Future<void> postData() async {
// final remarksData = getData();
print('sss$updaterUserIdForAPI');
final loggedInUserId = await getUserId();
final password = controllers["changePassword"]?.text ?? '';
final confirmPassword = controllers["confirmPassword"]?.text ?? '';
final String apiUrldata = '$apiUrl/api/user/user-password/$updaterUserIdForAPI';
final token = await getToken();
final headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
try {
final uri = Uri.parse(apiUrldata);
final headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
final body = jsonEncode({
"password": password,
"updated_by": loggedInUserId,
});
final response = await http.put(uri, headers: headers, body: body);
if (response.statusCode == 200 || response.statusCode == 201) {
print("Forex Details Created successfully!");
print("Response: ${response.body}");
_clearError();
Navigator.of(context).pop();
} else if (response.statusCode == 404) {
Navigator.of(context).pop();
final message = jsonDecode(response.body)['message'] ?? 'Unknown error';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.redAccent,
behavior: SnackBarBehavior.floating,
),
);
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting plan: $e");
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
// contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Row 1: Title + Edit + Delete buttons
Row(
children: [
Text(
'Change Password',
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
),
const Spacer(),
],
),
const SizedBox(height: 2),
Divider(
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
const SizedBox(height: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Email",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.330
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["email"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Email",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
),
if (errorMessages["email"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["email"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
SizedBox(
height: 10,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Change Password",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["changePassword"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Change Password",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
),
if (errorMessages["changePassword"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["changePassword"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
SizedBox(
height: 15,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Confirm Password",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["confirmPassword"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Confirm Password",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
),
if (errorMessages["confirmPassword"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["confirmPassword"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
child: ElevatedButton(
onPressed: () {
handleSubmit();
},
style: ElevatedButton.styleFrom(
backgroundColor: widget.layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Save',
style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)),
),
),
],
)
// : SizedBox.shrink(),
],
),
);
}
}

View File

@ -54,6 +54,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
String? userId; String? userId;
String? orgId; String? orgId;
String? userIdApi;
String? token; String? token;
@ -130,7 +131,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"delegationEndDate", "delegationEndDate",
// "dateOfIssue", // "dateOfIssue",
// "dateOfExpiry", // "dateOfExpiry",
"changePassword" "changePassword",
]; ];
Color? layoutColor; Color? layoutColor;
@ -201,7 +202,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("API Selected User Has Data - $apiselectedUser"); print("API Selected User Has Data - $apiselectedUser");
} }
userIdApi = apiselectedUser?["user_id"] ?? "";
controllers["Fname"]?.text = apiselectedUser?["first_name"] ?? ""; controllers["Fname"]?.text = apiselectedUser?["first_name"] ?? "";
controllers["Lname"]?.text = apiselectedUser?["last_name"] ?? ""; controllers["Lname"]?.text = apiselectedUser?["last_name"] ?? "";
controllers["email"]?.text = apiselectedUser?["email"] ?? ""; controllers["email"]?.text = apiselectedUser?["email"] ?? "";
@ -285,7 +286,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
if (apiselectedUser?["delegated_to_user_id"] != null) { if (apiselectedUser?["delegated_to_user_id"] != null) {
print( print(
"UPDADele- ${apiselectedUser?["delegated_to_user_id"]?.toString()}"); "UPDADele- ${apiselectedUser?["delegated_to_user_id"]?.toString()}",
);
selectedSubstituteApprover = selectedSubstituteApprover =
apiselectedUser?["delegated_to_user_id"]?.toString() ?? ""; apiselectedUser?["delegated_to_user_id"]?.toString() ?? "";
@ -302,11 +304,10 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
List<dynamic> decodedList = jsonDecode(fixedJson); List<dynamic> decodedList = jsonDecode(fixedJson);
selectedServiceIds = decodedList.map<Map<String, dynamic>>((item) { selectedServiceIds =
decodedList.map<Map<String, dynamic>>((item) {
final map = Map<String, dynamic>.from(item); final map = Map<String, dynamic>.from(item);
return { return {"service_id": map['service_id'].toString()};
"service_id": map['service_id'].toString(),
};
}).toList(); }).toList();
} catch (e) { } catch (e) {
print("❌ Error decoding fixed agent_supported_service_ids: $e"); print("❌ Error decoding fixed agent_supported_service_ids: $e");
@ -379,7 +380,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// userIdsApi = userMap.keys.toList(); // userIdsApi = userMap.keys.toList();
// Handle selectedUser as a Map (not a List) // Handle selectedUser as a Map (not a List)
apiselectedUser = extraData['selectedUser'] apiselectedUser =
extraData['selectedUser']
as Map<String, dynamic>?; // Cast it as a Map as Map<String, dynamic>?; // Cast it as a Map
isViewMode = extraData['isViewMode'] ?? false; isViewMode = extraData['isViewMode'] ?? false;
isEditProfile = extraData['isEditProfile'] ?? false; isEditProfile = extraData['isEditProfile'] ?? false;
@ -442,7 +444,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
userMap = { userMap = {
for (var user in userList) for (var user in userList)
user['user_id'].toString(): user['user_id'].toString():
"${user['first_name']} ${user['last_name']}" "${user['first_name']} ${user['last_name']}",
}; };
userIdsApi = userMap.keys.toList(); userIdsApi = userMap.keys.toList();
}); });
@ -480,11 +482,13 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
String? bodyStringColor = await getBodyColor(); String? bodyStringColor = await getBodyColor();
setState(() { setState(() {
layoutColor = layoutString != null layoutColor =
layoutString != null
? Color(int.parse(layoutString)) ? Color(int.parse(layoutString))
: Colors.redAccent; : Colors.redAccent;
bodyColor = bodyStringColor != null bodyColor =
bodyStringColor != null
? Color(int.parse(bodyStringColor)) ? Color(int.parse(bodyStringColor))
: Colors.white; : Colors.white;
}); });
@ -537,6 +541,11 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
} }
} }
void handleGoBack() async {
print("hello, please Go Back");
printFormData();
}
void handleNext() async { void handleNext() async {
print("USR Detail Next"); print("USR Detail Next");
printFormData(); printFormData();
@ -551,14 +560,6 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("USERDETAILS : $data"); print("USERDETAILS : $data");
if (!isValidData(data)) {
print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
} else {
print("USERDETAILS : $userDetials");
final tabs = { final tabs = {
"personal": "Personal Details", "personal": "Personal Details",
"office": "Office Details", "office": "Office Details",
@ -569,17 +570,57 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
final currentIndex = tabKeys.indexOf(selectedTab ?? "personal"); final currentIndex = tabKeys.indexOf(selectedTab ?? "personal");
print("currentIndex - $currentIndex");
bool isValid = false;
final currentTab = tabKeys[currentIndex];
if (currentTab == "personal") {
isValid = isValidData(userDetials);
} else if (currentTab == "office") {
isValid = isValidDataTwo(userDetials);
} else {
isValid = true; // Travel tab might not need validation at this point
}
if (!isValid) {
print("Validation Failed on $currentTab: $userDetials");
setState(() {}); // To trigger UI update showing errors
return;
}
if (currentIndex < tabKeys.length - 1) { if (currentIndex < tabKeys.length - 1) {
// Move to next tab // Move to next tab
setState(() { setState(() {
selectedTab = tabKeys[currentIndex + 1]; selectedTab = tabKeys[currentIndex + 1];
}); });
} else { } else {
// Already at last tab (travel), maybe submit form or show done message // Final step — submit or show done
print("All tabs completed!"); print("All tabs completed!");
// You can trigger full form submit here // Submit the full form here
}
} }
// if (!isValidData(data))
// {
// print("USERDETAILS : $userDetials");
// print("Validation Failed: Required fields are missing.");
// setState(() {});
// return; // Stop execution if validation fails
// } else
// {
// print("USERDETAILS : $userDetials");
//
// if (currentIndex < tabKeys.length - 1) {
// // Move to next tab
// setState(() {
// selectedTab = tabKeys[currentIndex + 1];
// });
// } else {
// // Already at last tab (travel), maybe submit form or show done message
// print("All tabs completed!");
// // You can trigger full form submit here
// }
// }
} }
void handleSubmit() async { void handleSubmit() async {
@ -600,7 +641,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// Map<String, dynamic> data = userDetials; // Map<String, dynamic> data = userDetials;
if (!isValidData(data)) { if (!isValidData(data) && isValidDataTwo(data)) {
print("USERDETAILS : $userDetials"); print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing."); print("Validation Failed: Required fields are missing.");
setState(() {}); setState(() {});
@ -622,7 +663,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"last_name", "last_name",
"email", "email",
"mobile_no", "mobile_no",
// "employeeCode" "role_id",
// "employeeCode",
]; ];
if (apiselectedUser == null) { if (apiselectedUser == null) {
@ -646,8 +688,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
if (data["alternate_mobile_no"] != null && if (data["alternate_mobile_no"] != null &&
data["alternate_mobile_no"].toString().isNotEmpty) { data["alternate_mobile_no"].toString().isNotEmpty) {
if (!RegExp(r"^\d{10}$") if (!RegExp(
.hasMatch(data["alternate_mobile_no"].toString())) { r"^\d{10}$",
).hasMatch(data["alternate_mobile_no"].toString())) {
errorMessages["alternate_mobile_no"] = errorMessages["alternate_mobile_no"] =
"Enter 10 digits"; // Invalid mobile number format "Enter 10 digits"; // Invalid mobile number format
} }
@ -655,14 +698,31 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// Email validation // Email validation
if (data["email"] != null && data["email"].toString().isNotEmpty) { if (data["email"] != null && data["email"].toString().isNotEmpty) {
if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") if (!RegExp(
.hasMatch(data["email"].toString())) { r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
).hasMatch(data["email"].toString())) {
errorMessages["email"] = "Invalid email format"; // Invalid email format errorMessages["email"] = "Invalid email format"; // Invalid email format
} }
} }
return errorMessages.isEmpty; // Valid if there are no errors return errorMessages.isEmpty; // Valid if there are no errors
} }
bool isValidDataTwo(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List<String> requiredFields = ["employee_code"];
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "Required";
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
void _clearError(String field) { void _clearError(String field) {
if (mounted && errorMessages.containsKey(field)) { if (mounted && errorMessages.containsKey(field)) {
setState(() { setState(() {
@ -813,8 +873,10 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("enteredPassword - $enteredPassword "); print("enteredPassword - $enteredPassword ");
if (hashedPassword != null && hashedPassword.isNotEmpty) { if (hashedPassword != null && hashedPassword.isNotEmpty) {
bool isMatch = bool isMatch = BCrypt.checkpw(
BCrypt.checkpw(enteredPassword, hashedPassword); // Compare passwords enteredPassword,
hashedPassword,
); // Compare passwords
setState(() { setState(() {
// ✅ Ensure UI updates // ✅ Ensure UI updates
@ -824,8 +886,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print(" Password match!"); print(" Password match!");
} else { } else {
print(" Password NOT match!"); print(" Password NOT match!");
errorMessages errorMessages.remove(
.remove("password"); // Clear error if password is different "password",
); // Clear error if password is different
} }
}); });
} else { } else {
@ -835,8 +898,10 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) { return ResponsiveBuilder(
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFf5f5f5), backgroundColor: Color(0xFFf5f5f5),
@ -845,22 +910,24 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
appBar: CustomAppBar(isDesktop: isDesktop), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding:
isDesktop
? EdgeInsets.symmetric( ? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding 0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height * vertical:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(0), : EdgeInsets.all(0),
child: Row( child: Row(
children: [ children: [Expanded(child: buildData(isDesktop, context))],
Expanded(child: buildData(isDesktop, context)),
],
), ),
), ),
); );
}); },
);
} }
Widget buildData(bool isDesktop, context) { Widget buildData(bool isDesktop, context) {
@ -874,7 +941,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
children: [ children: [
Expanded( Expanded(
child: Container( child: Container(
height: isDesktop height:
isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
child: Padding( child: Padding(
@ -887,24 +955,31 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
color: Colors.white, color: Colors.white,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: isDesktop child:
isDesktop
? Row( ? Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: (selectedTab == "travel" || children:
(selectedTab == "travel" ||
selectedRole == "5" || selectedRole == "5" ||
setSelectesUserType == true) setSelectesUserType == true)
? _buildSubmit(isDesktop, layoutColor!) ? _buildSubmit(isDesktop, layoutColor!)
: _buildNext(isDesktop, layoutColor!), : _buildNext(
isDesktop,
layoutColor!,
), // _buildGoBack(isDesktop, layoutColor!),
) )
: Row( : Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end,
children: (selectedTab == "travel" || children:
(selectedTab == "travel" ||
selectedRole == "5" || selectedRole == "5" ||
setSelectesUserType == true) setSelectesUserType == true)
? _buildSubmit(isDesktop, layoutColor!) ? _buildSubmit(isDesktop, layoutColor!)
: _buildNext(isDesktop, layoutColor!), : _buildNext(isDesktop, layoutColor!),
)), ),
) ),
),
], ],
), ),
); );
@ -943,9 +1018,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// }) // })
], ],
), ),
SizedBox( SizedBox(height: 18),
height: 18,
),
isDesktop isDesktop
? buildTabsForUser() ? buildTabsForUser()
: SingleChildScrollView( : SingleChildScrollView(
@ -954,14 +1027,13 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
), ),
Container( Container(
// color: Colors.yellow.shade50, // color: Colors.yellow.shade50,
height: MediaQuery.of(context).size.height * 0.64, height: MediaQuery.of(context).size.height * 0.64,
child: Row( child: Row(
children: [ children: [
Expanded(child: buildTabContents(isDesktop, isViewMode)), Expanded(child: buildTabContents(isDesktop, isViewMode)),
], ],
), ),
) ),
], ],
), ),
), ),
@ -976,6 +1048,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
personalDetailsKey: personalDetailsKey, personalDetailsKey: personalDetailsKey,
isDesktop: isDesktop, // pass isDesktop as a named argument isDesktop: isDesktop, // pass isDesktop as a named argument
isViewMode: isViewMode, isViewMode: isViewMode,
userIdApi: userIdApi,
controllers: controllers, controllers: controllers,
errorMessages: errorMessages, errorMessages: errorMessages,
selectedGender: selectedGender, selectedGender: selectedGender,
@ -1010,6 +1083,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
return OfficeDetails( return OfficeDetails(
isDesktop: isDesktop, // pass isDesktop as a named argument isDesktop: isDesktop, // pass isDesktop as a named argument
isViewMode: isViewMode, isViewMode: isViewMode,
userIdApi: userIdApi,
controllers: controllers, controllers: controllers,
errorMessages: errorMessages, errorMessages: errorMessages,
selectedLevel: selectedLevel, selectedLevel: selectedLevel,
@ -1052,6 +1126,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
}, },
); );
case "travel": case "travel":
final fullName =
"${controllers["Fname"]?.text ?? ""} ${controllers["Lname"]?.text ?? ""}"
.trim();
return TravellerDetails( return TravellerDetails(
key: travellerDetailsKey, key: travellerDetailsKey,
isDesktop: isDesktop, isDesktop: isDesktop,
@ -1060,12 +1137,15 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
errorMessages: errorMessages, errorMessages: errorMessages,
travelDetails: travelDetailsDataFromAPI, // 👈 Pass this down travelDetails: travelDetailsDataFromAPI, // 👈 Pass this down
passportFileUrlFromApi: passportFileUrlFromApi, passportFileUrlFromApi: passportFileUrlFromApi,
userIdApi: userIdApi,
fullName: fullName,
); );
default: default:
return PersonalDetails( return PersonalDetails(
personalDetailsKey: personalDetailsKey, personalDetailsKey: personalDetailsKey,
isDesktop: isDesktop, // pass isDesktop as a named argument isDesktop: isDesktop, // pass isDesktop as a named argument
isViewMode: isViewMode, isViewMode: isViewMode,
userIdApi: userIdApi,
controllers: controllers, controllers: controllers,
errorMessages: errorMessages, errorMessages: errorMessages,
selectedGender: selectedGender, selectedGender: selectedGender,
@ -1096,9 +1176,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
Map<String, String> getTabs(bool setSelectesUserType) { Map<String, String> getTabs(bool setSelectesUserType) {
if (setSelectesUserType || selectedRole == "5") { if (setSelectesUserType || selectedRole == "5") {
return { return {"personal": "Personal Details"};
"personal": "Personal Details",
};
} else { } else {
return allTabs; return allTabs;
} }
@ -1108,16 +1186,46 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.end, // important crossAxisAlignment: CrossAxisAlignment.end, // important
children: tabs.entries.map((entry) { children:
tabs.entries.map((entry) {
final targetTab = entry.key;
print("TargetsTAb: $targetTab");
final isSelected = selectedTab == entry.key; final isSelected = selectedTab == entry.key;
print("isSelected: $isSelected");
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
bool isValid = false;
final currentTab = selectedTab;
if (currentTab == "personal") {
isValid = isValidData(userDetials);
if (isValid) {
selectedTab = entry.key; selectedTab = entry.key;
}
} else if (currentTab == "office" &&
targetTab == "personal") {
selectedTab = entry.key;
} else if (currentTab == "office") {
isValid = isValidDataTwo(userDetials);
if (isValid) {
selectedTab = entry.key;
}
} else {
isValid =
true; // Travel tab might not need validation at this point
selectedTab = entry.key;
}
}); });
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 24.0), // space between tabs padding: const EdgeInsets.only(
right: 24.0,
), // space between tabs
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -1126,7 +1234,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: isSelected ? Color(0xFF114D8B) : Color(0xFF475569), color:
isSelected ? Color(0xFF114D8B) : Color(0xFF475569),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@ -1144,12 +1253,42 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
); );
} }
// ---- Submit --------------------------------------- // ---- Submit ---------------------------------------
List<Widget> _buildGoBack(isDesktop, Color layoutColor) {
return [
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: handleGoBack,
child: Text("Back"),
),
),
];
}
List<Widget> _buildNext(isDesktop, Color layoutColor) { List<Widget> _buildNext(isDesktop, Color layoutColor) {
return [ return [
MouseRegion( MouseRegion(
cursor: isViewMode cursor:
isViewMode
? SystemMouseCursors.forbidden ? SystemMouseCursors.forbidden
: SystemMouseCursors.click, : SystemMouseCursors.click,
child: ElevatedButton( child: ElevatedButton(
@ -1172,7 +1311,38 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// isViewMode ? null : handleNext, // Disable when in view mode // isViewMode ? null : handleNext, // Disable when in view mode
child: Text("Next"), child: Text("Next"),
), ),
) ),
];
}
List<Widget> _buildBack(isDesktop, Color layoutColor) {
return [
MouseRegion(
cursor:
isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: TextButton(
style: ElevatedButton.styleFrom(
backgroundColor:
isViewMode ? layoutColor : layoutColor, // Keep original color
foregroundColor:
isViewMode ? Colors.white : Colors.red, // 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: handleNext,
// onPressed:
// isViewMode ? null : handleNext, // Disable when in view mode
child: Text("Next"),
),
),
]; ];
} }
@ -1191,20 +1361,21 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
onPressed: () { onPressed: () {
isEditProfile ? context.go('/listPlan') : context.go('/listUser'); isEditProfile ? context.go('/listPlan') : context.go('/listUser');
}, },
child: Text("Cancel")), child: Text("Cancel"),
SizedBox(
width: 20,
), ),
SizedBox(width: 20),
if (!isViewMode) if (!isViewMode)
MouseRegion( MouseRegion(
cursor: isViewMode cursor:
isViewMode
? SystemMouseCursors.forbidden ? SystemMouseCursors.forbidden
: SystemMouseCursors.click, : SystemMouseCursors.click,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor:
isViewMode ? layoutColor : layoutColor, // Keep original color isViewMode ? layoutColor : layoutColor, // Keep original color
foregroundColor: isViewMode foregroundColor:
isViewMode
? Colors.white ? Colors.white
: Colors.white, // Keep original color : Colors.white, // Keep original color
disabledBackgroundColor: disabledBackgroundColor:
@ -1220,7 +1391,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
isViewMode ? null : handleSubmit, // Disable when in view mode isViewMode ? null : handleSubmit, // Disable when in view mode
child: Text("Submit"), child: Text("Submit"),
), ),
) ),
]; ];
} }
} }

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More