nhance_partner/lib/presentation/screens/staff/Enquiry/enquiry_inline_dec1.dart

4698 lines
161 KiB
Dart
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:ui';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:nhance_partner/presentation/screens/dashboard/dashboard_nw.dart';
import 'package:nhance_partner/presentation/screens/staff/Enquiry/quotationPopUpAccptReject.dart';
import 'package:nhance_partner/presentation/screens/staff/Enquiry/raise_enq_form.dart';
import 'package:nhance_partner/presentation/screens/staff/Enquiry/staff_policy.dart';
import 'package:nhance_partner/presentation/screens/staff/Enquiry/tabs/tab.dart';
import 'package:nhance_partner/presentation/themes/indicators/customizd_multi_file_upload.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../../core/config/env.dart';
import '../../../../core/routing/routes.dart';
import '../../../../core/services/api_service.dart';
import 'package:universal_html/html.dart' as html;
import '../../../../data/services/auth_service.dart';
import '../../../../data/utils/Pagination.dart';
import '../../../../data/utils/toastNotification.dart';
import '../../../../data/utils/validators.dart';
import '../../../layouts/main_layout.dart';
import '../../../layouts/responsive_layout.dart';
import '../../../providers/manager_provider.dart';
import '../../../providers/quotation_staff_proivder.dart';
import '../../../providers/userRoleProvider.dart';
import '../../../themes/indicators/customizd_file_upload.dart';
import '../../../themes/indicators/export_btn.dart';
import '../../../themes/indicators/input_field_decoration.dart';
import '../../../themes/indicators/search_field_theme.dart';
import '../../../themes/indicators/side_Drawer_Panel.dart';
import '../../../themes/indicators/text_field_theme.dart';
import '../../../themes/indicators/text_field_theme_inline_editor.dart'
hide UpperCaseTextFormatter;
import '../../../widgets/custom_Stdate_EnDate_Filter.dart';
import '../assignStaff.dart';
import 'multi_file_list.dart';
class EnquiryListStaffInline extends ConsumerStatefulWidget {
const EnquiryListStaffInline({super.key});
@override
ConsumerState<EnquiryListStaffInline> createState() => EnquiryStaffState();
}
class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
int currentPage = 1;
int itemsPerPage = 10;
late ApiService apiService;
dynamic userId;
dynamic idPrimary;
dynamic managerId;
dynamic dashboardKey;
dynamic SelectedStatus;
dynamic SelectedStaffId;
String? selectedFileNames;
PlatformFile? docUploadedFile;
dynamic handlerId;
String? _token;
final _formKey = GlobalKey<FormState>();
Map<String, String?> fieldErrors = {};
bool _showEditDrawer = false;
final raiseEnqKey = GlobalKey<RaiseEnqFormState>();
// List<Map<String, dynamic>> dataVal = [];
List<Map<String, dynamic>> getStaffData = [];
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
bool isLoading = false;
dynamic roleId;
bool _showFilterRow = false;
Map<int, bool> rowExpanded = {};
Map<String, TextEditingController> controllers = {};
List<String> tabHeader = [
'startDate',
'endDate',
'name',
'email',
'mobile',
'code',
'address',
'regNo',
'remarks',
];
Map<int, bool> isEditingRow = {};
Map<int, Map<String, TextEditingController>> rowControllers = {};
//---------------------- Enquiry TAB Initializations Starts -------------------------------- //
List<Map<String, dynamic>> getVehicleTypeData = [];
List<Map<String, dynamic>> filteredVechicleData = [];
List<Map<String, dynamic>> getAgentListData = [];
List<Map<String, dynamic>> filteredAgentData = [];
List<Map<String, dynamic>> getInsurersData = [];
List<Map<String, dynamic>> filteredInsurersData = [];
List<Map<String, dynamic>> getPaymentModeData = [];
List<Map<String, dynamic>> filteredPaymentModeData = [];
List<Map<String, dynamic>> getInsuranceTypeData = [];
List<Map<String, dynamic>> filteredInsuranceData = [];
String? selectedVehicleType;
String? selectedInsurer;
String? selectedInsPlanType;
String? selectedAgent;
PlatformFile? docUploadedRCFile;
PlatformFile? docUploadedIDProof;
PlatformFile? docUploadedPrevPolicy;
String? selectedPaymentMode;
String? selectedRCFile;
String? selectedIdProof;
String? selectedPrevPolicy;
String? selectedRCFileName;
String? rcFileUrlFromApi;
String? idProofFileUrlFromApi;
String? prevPolicyFileUrlFromApi;
String? selectedId;
Map<String, dynamic> dataDetails() {
final data = {
"agent_id":
((roleId == 'handler') ||
(roleId == 'manager') ||
(roleId == 'staff'))
? selectedAgent
: userId,
"name": controllers["name"]?.text,
"mobile": controllers["mobile"]?.text,
"email": controllers["email"]?.text,
"reg_no": controllers["regNo"]?.text,
"vehicle_type_id": selectedVehicleType,
// "is_data_created_by_handler": roleId == 'handler' ? '1' : '0',
// "is_data_created_by_manager": roleId == 'manager' ? '1' : '0',
"is_data_created_by_staff": roleId != null ? '1' : '0',
// "insurer_id": selectedInsurer,
// "rc_file_name": "rc_doc.pdf",
// "id_proof_file_name": "id_proof.pdf",
// "previous_policy_file_name": "previous_policy.pdf",
"remarks": controllers["remarks"]?.text,
"assigned_to": selectedStaff,
"insurer_id": selectedInsurer,
"broker_id": selectedBroker,
"manager_id": managerId,
"created_by": userId,
};
return data;
}
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
dropDownKeyInsurer = GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKeyAgent =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
dropDownSelectPaymentModeKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
//---------------------- Assign Staff starts -------------------------------- //
final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
dropDownKeyInsurerEnqAsgn =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKeyBroker =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
dropDownSelectStaffKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
List<Map<String, dynamic>> getStaffDetailsDataEnqAsgn = [];
List<Map<String, dynamic>> filteredStaffDataEnqAsgn = [];
String? selectedStaff;
String? selectedRegNum;
String? selectedStaffName;
List<Map<String, dynamic>> getBrokerData = [];
List<Map<String, dynamic>> filteredBrokerData = [];
String? selectedBroker;
final ScrollController _horizontalScrollController = ScrollController();
late Timer timer;
bool isActionable = false;
//---------------------- Assign Staff Ends -------------------------------- //
//---------------------- Enquiry TAB Initializations Ends -------------------------------- //
@override
void initState() {
super.initState();
apiService = ApiService();
timer = Timer.periodic(Duration(seconds: 30), (Timer t) => callRefresh());
for (String field in tabHeader) {
controllers[field] = TextEditingController();
}
_initializeToken();
Future.microtask(() async {
// final id = ref.read(managerIdProvider);
roleId = ref.read(userRoleProvider);
userId = ref.read(userIdProvider);
managerId = ref.read(managerIdProvider);
final prefs = await SharedPreferences.getInstance();
dashboardKey = prefs.getString('dashboardKeyProvider');
final dashboardStatus = prefs.getString('dashboardStatusProvider');
final dashboardStaffId = prefs.getString('dashboardStaffIdProvider');
print('handlerIdENQ - $handlerId');
print("C72 => r : $roleId | uId: $userId !mID : $managerId ");
print('dashboardKey - $dashboardKey');
print('dashboardStatus - $dashboardStatus');
print('dashboardStaffId - $dashboardStaffId');
if (managerId != null &&
dashboardKey != null &&
dashboardStatus != null &&
dashboardStaffId != null) {
print("DASHBOARD STATus-$dashboardStatus -- $dashboardStaffId -");
SelectedStatus = dashboardStatus;
SelectedStaffId = dashboardStaffId;
getStaffList(
managerId,
roleId,
SelectedStatus: dashboardStatus,
SelectedStaffId: dashboardStaffId,
);
} else {
print('ELSE');
getStaffList(managerId, roleId);
}
if (managerId != null) {
print('managerId - $managerId');
getAgentList(managerId);
}
final userID = ref.watch(userIdProvider);
print("managerId - $managerId");
if (userID != null) {
print('hansles');
getStaffDetailsForEnquiryAssignment(userID);
}
});
getVehicleType();
getInsurers();
getInsuranceType();
getBroker();
getPaymentMode();
}
Future<void> _initializeToken() async {
_token = await AuthService.getToken();
print("APISERTOKEN - $_token");
}
void callRefresh() {
print('Call Refresh');
if (!isActionable) {
print('Call Refresh isActionable');
refrshfilterDateRange();
}
}
void refresh() {
final id = ref.read(managerIdProvider);
final roleId = ref.read(userRoleProvider);
final userId = ref.read(userIdProvider);
print("C82 => r : $roleId | mId: $id | uId: $userId ");
if (managerId != null) {
getStaffList(managerId, roleId);
}
}
void filterDateRange() {
// ✅ Call your API
getStaffList(
managerId,
roleId,
SelectedStatus: SelectedStatus ?? '',
SelectedStaffId: SelectedStaffId ?? '',
);
}
@override
void dispose() {
_horizontalScrollController.dispose();
// ... dispose other controllers ...
super.dispose();
}
Future<void> refrshfilterDateRange() async {
print('refrshfilterDateRange');
// setState(() async {
final prefs = await SharedPreferences.getInstance();
// ✅ Clear any old data
await prefs.remove('dashboardKeyProvider');
await prefs.remove('dashboardStatusProvider');
await prefs.remove('dashboardStaffIdProvider');
SelectedStatus = '';
SelectedStaffId = null;
controllers['startDate']!.clear();
controllers['endDate']!.clear();
controllers['startDate']?.text = '';
controllers['endDate']?.text = '';
// Reset the FormField validation
_formKey.currentState?.reset();
// });
final int parsedManagerId = int.tryParse(managerId.toString()) ?? 0;
print('refrshfilterDateRange 1');
getStaffList(
// managerId,
parsedManagerId,
roleId,
SelectedStatus: SelectedStatus ?? '',
SelectedStaffId: SelectedStaffId ?? '',
);
print('refrshfilterDateRange 2');
}
Future<void> getInsurers() async {
print('Insurers called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchMasterDropDown('Insurers');
if (response['status'] == 200) {
print('getInsurers - ${response['data']}');
setState(() {
getInsurersData = List<Map<String, dynamic>>.from(response['data']);
print('API Data - $getInsurersData');
filteredInsurersData = List.from(getInsurersData);
print('originalData - $filteredInsurersData');
});
} else {
getInsurersData = [];
filteredInsurersData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getInsuranceType() async {
print('getClaimList called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchMasterDropDown('InsuranceType');
if (response['status'] == 200) {
print('getInsuranceTypeData - ${response['data']}');
setState(() {
getInsuranceTypeData = List<Map<String, dynamic>>.from(
response['data'],
);
print('API Data - $getInsuranceTypeData');
filteredInsuranceData = List.from(getInsuranceTypeData);
print('originalData - $filteredInsuranceData');
});
} else {
getInsuranceTypeData = [];
filteredInsuranceData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getBroker() async {
print('getBroker called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchMasterDropDown('Broker');
if (response['status'] == 200) {
print('getBroker - ${response['data']}');
setState(() {
getBrokerData = List<Map<String, dynamic>>.from(response['data']);
print('API Data - $getBrokerData');
filteredBrokerData = List.from(getBrokerData);
print('originalData - $filteredBrokerData');
});
} else {
getBrokerData = [];
filteredBrokerData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getPaymentMode() async {
print('getPaymentMode called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchMasterDropDown('PaymentMode');
if (response['status'] == 200) {
print('getPaymentModeData - ${response['data']}');
setState(() {
getPaymentModeData = List<Map<String, dynamic>>.from(
response['data'],
);
print('API Data - $getPaymentModeData');
filteredPaymentModeData = List.from(getPaymentModeData);
print('originalData - $filteredPaymentModeData');
});
} else {
getPaymentModeData = [];
filteredPaymentModeData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getStaffDetailsForEnquiryAssignment(int id) async {
print('getStaffDetailsForEnquiryAssignment called By handler');
setState(() {
isLoading = true;
});
try {
// final response = await apiService.fetchStaffUserList(id, role);
final response = await apiService.fetchStaffListForEnquiryAssignDropDown(
managerId,
id,
roleId,
);
if (response['status'] == 'success') {
print('getStaffDetailsForEnquiryAssignment - ${response['data']}');
setState(() {
getStaffDetailsDataEnqAsgn = List<Map<String, dynamic>>.from(
response['data'],
);
print('API Data - $getStaffDetailsDataEnqAsgn');
filteredStaffDataEnqAsgn = List.from(getStaffDetailsDataEnqAsgn);
print('originalData - $filteredStaffDataEnqAsgn');
});
} else {
getStaffDetailsDataEnqAsgn = [];
filteredStaffDataEnqAsgn = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getStaffList(
int managerId,
role, {
String fromDate = '',
String toDate = '',
String SelectedStatus = '',
String SelectedStaffId = '',
}) async {
print('A72 => Fns called => $managerId | $role');
setState(() {
isLoading = true;
});
dynamic fromDt;
dynamic toDt;
final fromDateVal = controllers['startDate']?.text ?? '';
final toDateVal = controllers['endDate']?.text ?? '';
final dateFormat = DateFormat('dd-MM-yyyy');
if (dashboardKey != 'fromDashboard' &&
fromDateVal == '' &&
toDateVal == '') {
print("ENQ LIST PAGE");
final today = DateTime.now();
fromDt = dateFormat.format(today.subtract(Duration(days: 15)));
toDt = dateFormat.format(today);
print('Enq FROM TO - $fromDt - $toDt');
} else {
fromDt = fromDateVal;
toDt = toDateVal;
}
try {
final response = await apiService.fetchEnquiryList(
managerId,
userId,
role,
fromDate: fromDt,
toDate: toDt,
selectedStatus: SelectedStatus != null ? SelectedStatus : '',
selectedStaffId: SelectedStaffId ?? '',
);
if (response['status'] == 'success') {
final data = response['data'];
final fromDate = response['from_date'] ?? '';
final toDate = response['to_date'] ?? '';
print('FromDate : $fromDate');
print('ToDate : $toDate');
setState(() {
controllers['startDate']?.text = fromDate;
controllers['endDate']?.text = toDate;
if (data is List) {
getStaffData = List<Map<String, dynamic>>.from(data);
} else if (data is Map) {
getStaffData = [Map<String, dynamic>.from(data)];
} else {
getStaffData = [];
}
originalData = getStaffData;
filteredData = List.from(originalData);
});
} else {
getStaffData = [];
originalData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
List<dynamic> get _paginatedData {
// Sort descending by id first
final sortedData = [...filteredData]
..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
if (sortedData.isEmpty) return [];
// Ensure currentPage is valid
final maxPage = (sortedData.length / itemsPerPage).ceil();
final safePage = currentPage.clamp(1, maxPage);
final startIndex = (safePage - 1) * itemsPerPage;
final endIndex = (startIndex + itemsPerPage).clamp(0, sortedData.length);
return sortedData.sublist(startIndex, endIndex);
}
void filterData(String query) {
print("FilterDAta - $query");
setState(() {
filteredData = getStaffData.where((item) {
// final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive";
return (item['agent_name'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['reg_no'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['insurer_name'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['assigned_to_name'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['insured_name'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['premium_amount'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['payment_mode'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(_formatDate(item['created_on']) ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(_formatDate(item['updated_on']) ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['policy_number'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['status'] ?? '-').toLowerCase().contains(query.toLowerCase());
}).toList();
});
}
final TextEditingController _searchStaffController = TextEditingController();
String _formatDate(String rawDate) {
try {
final dateTime = DateTime.parse(rawDate);
return DateFormat(
'dd-MM-yyyy HH:mm',
).format(dateTime); // 24-hour format
} catch (e) {
return rawDate; // fallback if parsing fails
}
}
Future<void> handleStaff(
BuildContext context,
dynamic data,
id,
regNum,
) async {
showDialog(
context: context,
builder: (ctx) => AssignStaffDialog(
enquiryPrimaryId: id,
regNum: regNum,
userId: userId,
onSubmit: (value) {
debugPrint("New assignY: $value");
refresh();
},
),
);
}
Future<void> handleProposalAccept(BuildContext context, String id) async {
final result = showDialog(
context: context,
barrierDismissible: true, // allows tapping outside to close
builder: (ctx) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
insetPadding: const EdgeInsets.all(24),
child: Container(
width: 500,
height: 300,
color: Colors.white,
child: QuotationPopUpTab(id: id, onRefresh: refrshfilterDateRange),
),
);
},
);
print("Dialog result: $result");
();
}
void handleEdit(Map<String, dynamic> item) {
print("handleEdit");
final id = int.tryParse(item['id'].toString()) ?? 0;
idPrimary = item['id'];
print("handleEdit - $idPrimary");
setState(() {
selectedId = idPrimary;
isEditingRow[id] = true;
});
for (final field in tabHeader) {
// Only set if controller exists
if (controllers.containsKey(field)) {
controllers[field]!.text = item[field] != null
? item[field].toString()
: ''; // fallback to empty string
}
}
//dropdowns or IDs
selectedVehicleType = item['vehicle_type_id'];
selectedAgent = item['agent_id'];
managerId = item['manager_id'];
controllers["regNo"]?.text = item['reg_no'];
controllers["remarks"]?.text = item['remarks'];
selectedInsurer = item['insurer_id'];
selectedStaff = item['assigned_to'];
setState(() {});
}
Future<void> handlePaymentMode(id, quotId) async {
Map<String, dynamic> data = {
"quotation_id": quotId,
"payment_mode_id": id,
"updated_by": userId,
};
final path = 'quotation/updateQuoteData';
final res = await apiService.createUserData(data, path);
print("handlePaymentMode - $res");
if (res['status'] == 'success') {
print("handlePaymentModesuccess");
refrshfilterDateRange();
}
}
Future<void> handleInsurancePlanTypeMode(id, quotId) async {
Map<String, dynamic> data = {
"quotation_id": quotId,
"insurance_plan_type_id": id,
"updated_by": userId,
};
final path = 'quotation/updateQuoteData';
final res = await apiService.createUserData(data, path);
print("handleINSMode - $res");
if (res['status'] == 'success') {
print("handleINSsuccess");
refrshfilterDateRange();
}
}
void handleSave(dynamic id) {
print('SAVE -- $id');
// final ids = int.tryParse(id.toString()) ?? 0;
idPrimary = id;
handleEnqTabSave();
}
void handleCancel(int id) {
print('handleCancel id - $id');
final bool isNewRow =
int.tryParse(id.toString()) != null &&
int.parse(id.toString()) > 1000000000000;
print('isNewRow - $isNewRow');
setState(() {
if (isNewRow) {
// Check if this is a new unsaved row
final row = getStaffData.firstWhere(
(item) => item['id'].toString() == id.toString(),
orElse: () => {},
);
// If policy_number is empty, it's a new row - remove it
if (row.isNotEmpty
// (row['policy_number'] == null ||
// row['policy_number'] == '' ||
// row['policy_number'] == '-')
) {
getStaffData.removeWhere(
(item) => item['id'].toString() == id.toString(),
);
originalData.removeWhere(
(item) => item['id'].toString() == id.toString(),
);
filteredData.removeWhere(
(item) => item['id'].toString() == id.toString(),
);
rowControllers.remove(id);
}
}
// Turn off editing mode
isEditingRow.remove(id);
// // Clear idPrimary if it matches
// if (idPrimary.toString() == id.toString()) {
// idPrimary = null;
// }
});
}
//-------------------------------Assign Logics staff start ---------------------------//
Widget buildSelectStaffMem(ctx) {
Map<String, dynamic>? selectedVehicle;
try {
selectedVehicle = filteredStaffDataEnqAsgn.firstWhere(
(item) => item['id'].toString() == selectedStaff,
);
} catch (e) {
selectedVehicle = null; // ✅ fallback
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Assigned To', style: _subLabelTimeStyle),
SizedBox(height: 5),
SizedBox(
height: 30,
width: MediaQuery.of(context).size.width * 0.08,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownSelectStaffKey,
// selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
selectedItem: selectedVehicle,
// items: (filter, infiniteScrollProps) {
// return filteredStaffDataEnqAsgn;
// },
items: (filter, infiniteScrollProps) async {
if (filter.isEmpty) {
return filteredStaffDataEnqAsgn;
}
return filteredStaffDataEnqAsgn.where((item) {
return item['name'].toString().toLowerCase().contains(
filter.toLowerCase(),
);
}).toList();
},
itemAsString: (val) => val['name'].toString(),
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // ✅ compare by id
validator: (val) {
if (val == null) {
return "Required"; // ✅ error message
}
return null;
},
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
isVisible: true,
padding: EdgeInsets.zero, // remove default padding
constraints: const BoxConstraints(
// shrink icon tap area
minWidth: 12,
minHeight: 12,
),
iconSize: 15, // smaller icon
// icon: const Icon(Icons.arrow_drop_down),
),
),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Staff Member",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0XFF6366F1),
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0XFF6366F1),
width: 0.5,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null ? selectedItem['name'].toString() : "",
style: GoogleFonts.poppins(
fontSize: 11,
color: Color(0XFF6366F1),
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
autofocus: true,
style: GoogleFonts.inter(fontSize: 11, color: Colors.black),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(1),
filled: true,
fillColor: Colors.white,
hintText: "Search Staff ...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
// constraints: BoxConstraints(),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(fontSize: 12, color: Colors.black),
),
);
},
),
onChanged: (val) {
if (val != null) {
print("Selected Staff : ${val['name']}");
print("Id: ${val['id']}");
selectedStaffName = val['name'];
selectedStaff = val['id'];
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
),
],
);
}
Widget buildInsurer(BuildContext context, {fromHeader = true}) {
Map<String, dynamic>? selectedInsurerd = filteredInsurersData.firstWhere(
(item) => item['id'].toString() == selectedInsurer,
orElse: () => {},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Insurer', style: _subLabelTimeStyle),
SizedBox(height: 5),
Container(
height: 30,
width: fromHeader
? MediaQuery.of(context).size.width * 0.1
: MediaQuery.of(context).size.width * 0.08,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKeyInsurerEnqAsgn,
selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null,
// items: (filter, infiniteScrollProps) {
// return filteredInsurersData;
// },
items: (filter, infiniteScrollProps) async {
if (filter.isEmpty) {
return filteredInsurersData;
}
return filteredInsurersData.where((item) {
return item['name'].toString().toLowerCase().contains(
filter.toLowerCase(),
);
}).toList();
},
itemAsString: (val) => val['name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // ✅ compare by id
validator: (val) {
if (val == null) {
return "Required"; // ✅ error message
}
return null;
},
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
isVisible: true,
padding: EdgeInsets.zero, // remove default padding
constraints: const BoxConstraints(
// shrink icon tap area
minWidth: 12,
minHeight: 12,
),
iconSize: 15, // smaller icon
// icon: const Icon(Icons.arrow_drop_down),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null ? selectedItem['name'].toString() : "",
style: GoogleFonts.poppins(
fontSize: 11,
color: Color(0XFF6366F1),
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Insurer",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0XFF6366F1),
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0XFF6366F1),
width: 0.5,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 200),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
style: GoogleFonts.inter(fontSize: 11, color: Colors.black),
autofocus: true,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(1),
filled: true,
fillColor: Colors.white,
hintText: "Search Insurer...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(fontSize: 12, color: Colors.black),
),
);
},
// constraints: BoxConstraints(),
),
onChanged: (val) {
if (val != null) {
print("Selected Insurer : ${val['name']}");
print("Id: ${val['id']}");
selectedInsurer = val['id'];
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
),
],
);
}
Widget buildBroker(BuildContext context) {
Map<String, dynamic>? selectedBrokers = filteredBrokerData.firstWhere(
(item) => item['id'].toString() == selectedBroker,
orElse: () => {},
);
return SizedBox(
height: 30,
width: MediaQuery.of(context).size.width * 0.07,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKeyBroker,
selectedItem: selectedBrokers.isNotEmpty ? selectedBrokers : null,
items: (filter, infiniteScrollProps) {
return filteredBrokerData;
},
itemAsString: (val) => val['name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // ✅ compare by id
validator: (val) {
if (val == null) {
return "Required"; // ✅ error message
}
return null;
},
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
isVisible: true,
padding: EdgeInsets.zero, // remove default padding
constraints: const BoxConstraints(
// shrink icon tap area
minWidth: 12,
minHeight: 12,
),
iconSize: 15, // smaller icon
// icon: const Icon(Icons.arrow_drop_down),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null ? selectedItem['name'].toString() : "",
style: GoogleFonts.poppins(fontSize: 11, color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Broker",
).copyWith(
filled: true,
fillColor: Colors.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(color: Colors.black, width: 0.1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(color: Colors.black, width: 0.1),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
style: GoogleFonts.inter(fontSize: 11, color: Colors.black),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(1),
filled: true,
fillColor: Colors.white,
hintText: "Search Broker...",
hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(fontSize: 12, color: Colors.black),
),
);
},
// constraints: BoxConstraints(),
),
onChanged: (val) {
if (val != null) {
print("Selected Broker : ${val['name']}");
print("Id: ${val['id']}");
selectedBroker = val['id'];
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
);
}
// -------------------------------Assign Logics staff end---------------------------//
//---------------------- Enquiry TAB Logins Starts -------------------------------- //
Map<String, dynamic> dataEnquiryTabDetails() {
final data = {
"agent_id": ((roleId == 'handler') || (roleId == 'manager'))
? selectedAgent
: userId,
"name": controllers["name"]?.text,
"mobile": controllers["mobile"]?.text,
"email": controllers["email"]?.text,
"reg_no": controllers["regNo"]?.text,
"vehicle_type_id": selectedVehicleType,
"is_data_created_by_handler": roleId == 'handler' ? '1' : '0',
"is_data_created_by_manager": roleId == 'manager' ? '1' : '0',
// "insurer_id": selectedInsurer,
// "rc_file_name": "rc_doc.pdf",
// "id_proof_file_name": "id_proof.pdf",
// "previous_policy_file_name": "previous_policy.pdf",
"remarks": controllers["remarks"]?.text,
"manager_id": managerId,
"created_by": userId,
};
return data;
}
Future<void> getVehicleType() async {
print('getClaimList called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchMasterDropDown('vehicleType','dropdown');
if (response['status'] == 200) {
print('getVehicleTypeData - ${response['data']}');
setState(() {
getVehicleTypeData = List<Map<String, dynamic>>.from(
response['data'],
);
print('API Data - $getVehicleTypeData');
filteredVechicleData = List.from(getVehicleTypeData);
print('originalData - $filteredVechicleData');
});
} else {
getVehicleTypeData = [];
filteredVechicleData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getAgentList(id) async {
print('getAgentListData called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchAgentNameDropDown(id);
print('getAgentListData called response');
print('get Agent- ${response['data']}');
if (response['status'] == 'success') {
print('get Agent- ${response['data']}');
setState(() {
getAgentListData = List<Map<String, dynamic>>.from(response['data']);
print('API Data - $getAgentListData');
filteredAgentData = List.from(getAgentListData);
print('originalAgentData - $filteredAgentData');
});
} else {
getAgentListData = [];
filteredAgentData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Widget buildAgentName(BuildContext context, {fromHeader = true}) {
Map<String, dynamic>? selectedAgntName = filteredAgentData.firstWhere(
(item) => item['id'].toString() == selectedAgent,
orElse: () => {},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Partner', style: _subLabelTimeStyle),
SizedBox(height: 5),
SizedBox(
width: MediaQuery.of(context).size.width * 0.08,
// width: fromHeader
// ? MediaQuery.of(context).size.width * 0.11
// : MediaQuery.of(context).size.width * 0.08,
height: 30,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKeyAgent,
selectedItem: selectedAgntName.isNotEmpty ? selectedAgntName : null,
// items: (filter, infiniteScrollProps) {
// return filteredAgentData;
// },
items: (filter, infiniteScrollProps) async {
if (filter.isEmpty) {
return filteredAgentData;
}
return filteredAgentData.where((item) {
return item['name'].toString().toLowerCase().contains(
filter.toLowerCase(),
);
}).toList();
},
itemAsString: (val) => val['name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // ✅ compare by id
validator: (val) {
if (val == null) {
return "Required"; // ✅ error message
}
return null;
},
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
isVisible: true,
padding: EdgeInsets.zero, // remove default padding
constraints: const BoxConstraints(
// shrink icon tap area
minWidth: 12,
minHeight: 12,
),
iconSize: 15, // smaller icon
// icon: const Icon(Icons.arrow_drop_down),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null
? selectedItem['name'].toString()
: "", // ✅ FIXED
style: GoogleFonts.poppins(
fontSize: 11,
color: Color(0XFF6366F1),
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Partner Name",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0XFF6366F1),
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0XFF6366F1),
width: 0.5,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
autofocus: true,
style: GoogleFonts.inter(fontSize: 11, color: Colors.black),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(1),
filled: true,
fillColor: Colors.white,
hintText: "Partner Name",
hintStyle: GoogleFonts.inter(
fontSize: 10,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
// constraints: BoxConstraints(),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item['name'].toString(),
style: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
),
Text(
item['agent_code'].toString(),
style: GoogleFonts.inter(
fontSize: 11,
color: Colors.grey,
),
),
],
),
);
},
),
onChanged: (val) {
if (val != null) {
print("Selected Partner : ${val['name']}");
print("Id: ${val['id']}");
selectedAgent = val['id'];
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
),
],
);
}
Widget buildVehicleNumber(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Vehicle Number', style: _subLabelTimeStyle),
SizedBox(height: 5),
SizedBox(
width: MediaQuery.of(context).size.width * 0.08,
child: ThemedFormInlineField(
controller: controllers['regNo']!,
hintText: 'Vehicle Number',
borderColor: Color(0XFF6366F1),
textColr: Color(0XFF6366F1),
isdense: true,
inputFormatters: [
UpperCaseTextFormatter(), // 👈 custom formatter for uppercase
FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')),
],
validator: (value) => Validators.requiredVechileNum(value, "regNo"),
widthNone: true,
),
),
],
);
}
Widget buildInsuredName(BuildContext context, {fromHeader = true}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Insured', style: _subLabelTimeStyle),
SizedBox(height: 5),
SizedBox(
width: fromHeader
? MediaQuery.of(context).size.width * 0.1
: MediaQuery.of(context).size.width * 0.08,
child: ThemedFormInlineField(
controller: controllers['name']!,
hintText: 'Insured Name',
textColr: Color(0XFF6366F1),
// errorText: fieldErrors['name'],
borderColor: Color(0XFF6366F1),
isdense: true,
validator: (value) => Validators.requiredField(value, "name"),
widthNone: true,
),
),
],
);
}
Widget buildVehicleType(BuildContext context) {
// Find the matching map from your list
Map<String, dynamic>? selectedVehicle = filteredVechicleData.firstWhere(
(item) => item['id'].toString() == selectedVehicleType,
orElse: () => {},
);
return SizedBox(
width: MediaQuery.of(context).size.width * 0.07,
height: 30,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKey,
selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
items: (filter, infiniteScrollProps) {
return filteredVechicleData;
},
validator: (val) {
if (val == null) {
return "Required"; // ✅ error message
}
return null;
},
itemAsString: (val) => val['vehicle_type'].toString(),
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // ✅ compare by id
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
isVisible: true,
padding: EdgeInsets.zero, // remove default padding
constraints: const BoxConstraints(
// shrink icon tap area
minWidth: 12,
minHeight: 12,
),
iconSize: 15, // smaller icon
// icon: const Icon(Icons.arrow_drop_down),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null
? selectedItem['vehicle_type'].toString()
: "", // ✅ FIXED
style: GoogleFonts.poppins(fontSize: 11, color: Color(0XFF6366F1)),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Vehicle Type",
).copyWith(
filled: true,
fillColor: Colors.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(color: Colors.black, width: 0.1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(color: Colors.black, width: 0.1),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 200),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
style: GoogleFonts.inter(fontSize: 11, color: Colors.black),
padding: const EdgeInsets.all(1.0),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(1),
filled: true,
fillColor: Colors.white,
hintText: "Search Vehicle Type...",
hintStyle: GoogleFonts.inter(fontSize: 10, color: Colors.black),
labelStyle: GoogleFonts.inter(fontSize: 10, color: Colors.black),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white), // 👈 Normal border
),
isDense: true, // reduces height
// contentPadding: const EdgeInsets.symmetric(
// horizontal: 8,
// vertical: 8, // reduce this value for smaller height
// ),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
// constraints: BoxConstraints(),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
child: Text(
item['vehicle_type'].toString(),
style: GoogleFonts.inter(fontSize: 12, color: Colors.black),
),
);
},
),
onChanged: (val) {
if (val != null) {
print("Selected vehicle_type : ${val['vehicle_type']}");
print("Id: ${val['id']}");
selectedVehicleType = val['id'];
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
);
}
Widget buildEmail(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Email', style: _subLabelTimeStyle),
SizedBox(height: 5),
SizedBox(
width: MediaQuery.of(context).size.width * 0.1,
child: ThemedFormInlineField(
controller: controllers['email']!,
borderColor: Color(0XFF6366F1),
textColr: Color(0XFF6366F1),
hintText: 'Email',
isdense: true,
validator: (value) => Validators.nonReqemail(value, "email"),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')),
],
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.16,
),
),
],
);
}
Widget buildPhNumber(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Mobile', style: _subLabelTimeStyle),
SizedBox(height: 5),
SizedBox(
width: MediaQuery.of(context).size.width * 0.1,
child: ThemedFormInlineField(
controller: controllers['mobile']!,
borderColor: Color(0XFF6366F1),
textColr: Color(0XFF6366F1),
hintText: 'Mobile',
isdense: true,
validator: (value) => Validators.nonReqphone(value, "phone"),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[ 0-9]')),
],
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
),
),
],
);
}
Widget buildRemarks(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Remarks', style: _subLabelTimeStyle),
SizedBox(height: 5),
ThemedFormField(
// maxLength: 500,
enableBorderWidth: 0.5,
hintText: 'Remarks',
controller: controllers['remarks']!,
borderColor: Color(0XFF6366F1),
textColr: Color(0XFF6366F1),
verticalPad: 10,
horizonalPad: 10,
isdense: true,
// keyboardType: TextInputType.multiline,
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.1,
),
],
);
}
Widget buildPaymentMode(context) {
Map<String, dynamic>? selectedVehicle = filteredPaymentModeData.firstWhere(
(item) => item['id'].toString() == selectedPaymentMode,
orElse: () => {},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Payment Mode", style: _textStyle),
// SizedBox(height: 10),
SizedBox(
// color: Colors.red,
// color: Colors.yellow,
width: 200,
// height: 30,
// width: ResponsiveLayout.isMobile(context)
// ? null
// : MediaQuery.of(context).size.width * 0.18,
// height: 35,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownSelectPaymentModeKey,
selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
// items: (filter, infiniteScrollProps) {
// return filteredInsuranceData;
// },
items: (filter, infiniteScrollProps) async {
if (filter.isEmpty) {
return filteredPaymentModeData;
}
return filteredPaymentModeData.where((item) {
return item['name'].toString().toLowerCase().contains(
filter.toLowerCase(),
);
}).toList();
},
itemAsString: (val) => val['value'].toString(),
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // ✅ compare by id
validator: (val) {
if (val == null) {
return "Required"; // ✅ error message
}
return null;
},
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Payment Mode",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0xFFE2E8F0),
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0xFFE2E8F0),
width: 0.5,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupProps.dialog(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 400, maxWidth: 300),
// constraints: BoxConstraints(maxHeight: 250),
dialogProps: DialogProps(backgroundColor: Colors.white),
// menuProps: MenuProps(
// backgroundColor:
// Colors.white, // 👈 sets dropdown background to white
// ),
showSearchBox: true,
searchFieldProps: TextFieldProps(
autofocus: true,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(1),
filled: true,
fillColor: Colors.white,
hintText: "Search Payment Mode ...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['value'].toString(),
style: GoogleFonts.inter(fontSize: 12, color: Colors.black),
),
);
},
),
onChanged: (val) {
if (val != null) {
print("Selected value : ${val['value']}");
print("Id: ${val['id']}");
selectedPaymentMode = val['id'];
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
),
],
);
}
Widget buildResponsiveUploadField({
required String label,
required String? hintText,
required double? width,
required void Function(String fileName, dynamic file) onFileSelected,
bool showDownload = false,
VoidCallback? onRemove,
VoidCallback? onDownload,
}) {
final isMobile = ResponsiveLayout.isMobile(context);
final uploadWidget = ThemedMultiFileUploadField(
hintText: hintText ?? "Upload Document",
txtwidth: width,
// txtwidth: MediaQuery.of(context).size.width * 0.08,
padVertical: 6,
padHorizontal: 8,
backgroundColor: Colors.white,
// backgroundColor: Color(0xffD9EBE8),
txtName: label,
isTxtBtnCase: true,
onFileSelected: onFileSelected,
);
// final uploadWidget = ThemedUploadField(
// hintText: hintText ?? "Upload Document",
// txtwidth: width,
// // txtwidth: MediaQuery.of(context).size.width * 0.08,
// padVertical: 6,
// padHorizontal: 8,
//
// backgroundColor: Colors.white,
// // backgroundColor: Color(0xffD9EBE8),
// txtName: label,
// isTxtBtnCase: true,
// onFileSelected: onFileSelected,
// );
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [uploadWidget],
),
],
);
}
Widget buildUploadRCDocument(BuildContext context) {
return SizedBox(
// width: MediaQuery.of(context).size.width * 0.08,
child: buildResponsiveUploadField(
label: "RC",
width: 60,
hintText: selectedRCFile,
onFileSelected: (fileName, file) {
setState(() {
docUploadedRCFile = file;
selectedRCFileName = fileName;
});
ToastHelper.showSuccessToast(context, 'File Uploaded Successfully');
},
showDownload: docUploadedRCFile != null || rcFileUrlFromApi != null,
onRemove: () {
setState(() {
docUploadedRCFile = null;
rcFileUrlFromApi = null;
selectedRCFileName = null;
});
},
onDownload: () => apiService.downloadFile(
// apiUrl: 'agent/downloadAgentCertificateFile?agent_id=$selectedId',
apiUrl:
'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc',
apiId: selectedId.toString(),
localFile: docUploadedRCFile,
fileName: 'RC',
),
),
);
}
Widget buildUploadIDDocument(BuildContext context) {
return SizedBox(
// width: MediaQuery.of(context).size.width * 0.08,
child: buildResponsiveUploadField(
label: "ID Proof",
width: 80,
hintText: selectedIdProof,
onFileSelected: (fileName, file) {
setState(() {
docUploadedIDProof = file;
// selectedFileNames = fileName;
});
ToastHelper.showSuccessToast(context, 'File Uploaded Successfully');
},
showDownload:
docUploadedIDProof != null || idProofFileUrlFromApi != null,
onRemove: () {
setState(() {
docUploadedIDProof = null;
idProofFileUrlFromApi = null;
// selectedFileNames = null;
});
},
onDownload: () => apiService.downloadFile(
// apiUrl: 'agent/downloadAgentCertificateFile?agent_id=$selectedId',
apiUrl:
'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof',
apiId: selectedId.toString(),
localFile: docUploadedIDProof,
fileName: 'Id_Proof',
),
),
);
}
Widget buildUploadPolicyDocument(BuildContext context) {
return SizedBox(
// width: MediaQuery.of(context).size.width * 0.08,
child: buildResponsiveUploadField(
width: 100,
label: "PrevPolicy",
hintText: selectedPrevPolicy,
onFileSelected: (fileName, file) {
setState(() {
docUploadedPrevPolicy = file;
// selectedFileNames = fileName;
});
ToastHelper.showSuccessToast(context, 'File Uploaded Successfully');
},
showDownload:
docUploadedPrevPolicy != null || prevPolicyFileUrlFromApi != null,
onRemove: () {
setState(() {
docUploadedPrevPolicy = null;
prevPolicyFileUrlFromApi = null;
// selectedFileNames = null;
});
},
onDownload: () => apiService.downloadFile(
// apiUrl: 'agent/downloadAgentCertificateFile?agent_id=$selectedId',
apiUrl:
'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy',
apiId: selectedId.toString(),
localFile: docUploadedPrevPolicy,
fileName: 'Previous_Policy',
),
),
);
}
Widget buildDocRC(BuildContext context, selectedEnquiryId) {
return SizedBox(
width: MediaQuery.of(context).size.width * 0.16,
child: InkWell(
onTap: () {
print("Upload $selectedEnquiryId");
final selectedId = selectedEnquiryId;
// final path =
// 'agent/downloadAgentIncentiveFile?id=$selectedId';
final path =
'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc';
print("Uploadpath $path");
apiService.getPdfDownload(path, selectedId);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: const Color(0xFF425B5B),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Tooltip(
message: 'Download RC',
child: Text(
"RC",
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
],
),
),
),
);
}
Widget buildDocIdProof(BuildContext context, selectedEnquiryId) {
return InkWell(
onTap: () {
print("Upload $selectedEnquiryId");
final selectedId = selectedEnquiryId;
// final path =
// 'agent/downloadAgentIncentiveFile?id=$selectedId';
final path =
'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof';
print("Uploadpath $path");
apiService.getPdfDownload(path, selectedId);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: const Color(0xFF425B5B),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Tooltip(
message: 'Download ID Proof',
child: Text(
"ID Proof",
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
],
),
),
);
}
Widget buildDocPrevPolicy(BuildContext context, selectedEnquiryId) {
return InkWell(
onTap: () {
print("QD597 Upload $selectedEnquiryId");
final selectedId = selectedEnquiryId;
// final path =
// 'agent/downloadAgentIncentiveFile?id=$selectedId';
final path =
'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy';
print("Uploadpath $path");
apiService.getPdfDownload(path, selectedId);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: const Color(0xFF425B5B),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Tooltip(
message: 'Download Previous Policy',
child: Text(
"Prev Policy",
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
],
),
),
);
}
// Update validation method to set errors
bool _validateRequiredFields() {
setState(() {
fieldErrors.clear(); // Clear previous errors
// Validate Partner/Agent
if (selectedAgent == null || selectedAgent == '') {
fieldErrors['agent'] = 'Partner is required';
}
// Validate Insured Name
if (controllers['name']?.text.trim().isEmpty ?? true) {
fieldErrors['name'] = 'Insured Name is required';
}
// Validate Vehicle Number
if (controllers['regNo']?.text.trim().isEmpty ?? true) {
fieldErrors['regNo'] = 'Vehicle Number is required';
}
// Validate Vehicle Type
if (selectedVehicleType == null || selectedVehicleType == '') {
fieldErrors['vehicleType'] = 'Vehicle Type is required';
}
// Validate email format if provided
final email = controllers['email']?.text.trim() ?? '';
if (email.isNotEmpty) {
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(email)) {
fieldErrors['email'] = 'Invalid email format';
}
}
// Validate mobile number if provided
final mobile = controllers['mobile']?.text.trim() ?? '';
if (mobile.isNotEmpty) {
final cleanMobile = mobile.replaceAll(' ', '');
if (cleanMobile.length < 10) {
fieldErrors['mobile'] = 'Must be at least 10 digits';
}
}
});
return true;
return fieldErrors.isEmpty;
}
Future<void> handleEnqTabSave() async {
if (!_validateRequiredFields()) return;
// setState(() {
// isSaving = true; // start saving
// });
final dataSet = dataDetails();
try {
await createUserData(dataSet); // API call
// ToastHelper.showInfoToast(context, "Enquiry saved successfully");
} catch (e) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Error'),
content: Text(e.toString()),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('OK'),
),
],
),
);
} finally {
// setState(() => isSaving = false); // re-enable button after save
}
}
Future<void> attachFiles(http.MultipartRequest request) async {
Future<void> addFileOrKeepName(
PlatformFile? file,
String? apiFileName,
String fieldName,
) async {
if (file != null) {
// User uploaded a new file → send as multipart
if (file.bytes != null) {
request.files.add(
http.MultipartFile.fromBytes(
fieldName,
file.bytes!,
filename: file.name,
),
);
} else if (file.path != null) {
request.files.add(
await http.MultipartFile.fromPath(
fieldName,
file.path!,
filename: file.name,
),
);
}
print("📎 Attached new file → $fieldName");
} else if (apiFileName != null && apiFileName.isNotEmpty) {
// No new upload → tell backend to keep old file
request.fields[fieldName] = apiFileName;
print("🔗 Kept old file → $fieldName = $apiFileName");
} else {
// Nothing at all
request.fields[fieldName] = "";
}
}
await addFileOrKeepName(
docUploadedRCFile,
rcFileUrlFromApi,
'rc_file_name',
);
await addFileOrKeepName(
docUploadedIDProof,
idProofFileUrlFromApi,
'id_proof_file_name',
);
await addFileOrKeepName(
docUploadedPrevPolicy,
prevPolicyFileUrlFromApi,
'previous_policy_file_name',
);
}
List<Widget> _buildPopupDownload(BuildContext context, selectId) {
bool isMobile = ResponsiveLayout.isMobile(context);
return [
// editing
// ?
Container(
// color: Colors.red.shade50,
padding: EdgeInsets.only(top: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
buildUploadRCDocument(context),
SizedBox(width: 3),
buildUploadIDDocument(context),
SizedBox(width: 3),
buildUploadPolicyDocument(context),
],
),
),
];
}
Widget _buildSubRow(context, selectId, id) {
return Wrap(
spacing: 12,
runSpacing: 12,
children: [
buildSelectStaffMem(context),
buildAgentName(context, fromHeader: false),
buildInsurer(context),
buildInsuredName(context),
buildVehicleNumber(context),
buildEmail(context),
buildPhNumber(context),
buildRemarks(context),
// Padding(
// padding: const EdgeInsets.only(top: 8.0),
// child: Builder(
// builder: (buttonContext) => Material(
// // color: Color(0xFFFFFCF2),
// color: Color(0xFFF8F9FF),
// child: InkWell(
// hoverColor: Color(0xFFF8F9FF),
// // hoverColor: const Color(0xFFFFFCF2),
// // hoverColor: const Color(0xFFEAF6F4),
// onTap: () async {
// // ✅ Get overlay and button position RELATIVE to InkWell
// final RenderBox button =
// buttonContext.findRenderObject() as RenderBox;
// final RenderBox overlay =
// Overlay.of(buttonContext).context.findRenderObject()
// as RenderBox;
//
// final Offset position = button.localToGlobal(
// Offset.zero,
// ancestor: overlay,
// );
//
// await showMenu(
// context: buttonContext,
// position: RelativeRect.fromLTRB(
// position.dx,
// position.dy + button.size.height,
// overlay.size.width,
// 0,
// ),
// items: [
// PopupMenuItem(
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: _buildPopupDownload(
// buttonContext,
// selectId,
// ),
// ),
// ),
// ],
// // color: Colors.white,
// // color: Color(0xFFFFFCF2),
// color: Color(0xFFF8F9FF),
// );
// },
// child: Tooltip(
// message: 'Click To Upload Documents',
//
// waitDuration: const Duration(milliseconds: 500),
// showDuration: const Duration(seconds: 2),
// child: Container(
// padding: EdgeInsets.all(8.0),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(50),
// ),
// child: Icon(
// Icons.upload_file,
// color: Color(0XFF6366F1),
// // editing ? Icons.upload_file : Icons.download_for_offline,
// // color: editing ? Colors.b lue : Colors.green,
// size: 20,
// ),
// ),
// ),
// ),
// ),
// ),
// ),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: IconButton(
tooltip: 'Save',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 24, minHeight: 24),
icon: const Icon(
Icons.check,
color: Colors.green,
size: 28, // smaller icon
),
splashRadius: 14, // ripple radius
onPressed: () => {
setState(() {
rowExpanded[id] = !(rowExpanded[id] ?? false);
}),
handleSave(selectId),
},
),
),
],
);
}
List<Widget> _buildPopupDownload_BTN(
BuildContext context,
dynamic data,
id,
regNum,
) {
bool isMobile = ResponsiveLayout.isMobile(context);
return [
Row(
children: [
buildDocRC_BTN(context, isMobile, id),
Spacer(),
// SizedBox(width: 3),
buildDocIdProof_BTN(context, isMobile, id),
Spacer(),
buildDocPrevPolicy_BTN(context, isMobile, id),
],
),
];
}
Widget buildDocRC_BTN(
BuildContext context,
bool isMobile,
selectedEnquiryId,
) {
return GestureDetector(
onTap: () {
final selectedId = selectedEnquiryId;
// final path =
// 'agent/downloadAgentIncentiveFile?id=$selectedId';
final path =
'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc';
apiService.getPdfDownload(path, selectedId);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: const Color(0xFF425B5B),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"RC",
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
?isMobile ? null : SizedBox(width: 2),
// isMobile ? const Spacer() : const SizedBox(width: 15),
const Icon(
Icons.file_download_outlined,
size: 16,
color: Colors.white,
),
],
),
),
);
}
Widget buildDocIdProof_BTN(
BuildContext context,
bool isMobile,
selectedEnquiryId,
) {
return GestureDetector(
onTap: () {
print("Upload $selectedEnquiryId");
final selectedId = selectedEnquiryId;
// final path =
// 'agent/downloadAgentIncentiveFile?id=$selectedId';
final path =
'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof';
apiService.getPdfDownload(path, selectedId);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: const Color(0xFF425B5B),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"ID Proof",
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
?isMobile ? null : SizedBox(width: 2),
// const SizedBox(width: 15),
Icon(Icons.file_download_outlined, size: 16, color: Colors.white),
],
),
),
);
}
Widget buildDocPrevPolicy_BTN(
BuildContext context,
bool isMobile,
selectedEnquiryId,
) {
return GestureDetector(
onTap: () {
print("QD597 Upload $selectedEnquiryId");
final selectedId = selectedEnquiryId;
// final path =
// 'agent/downloadAgentIncentiveFile?id=$selectedId';
final path =
'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy';
apiService.getPdfDownload(path, selectedId);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: const Color(0xFF425B5B),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Previous Policy",
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
?isMobile ? null : SizedBox(width: 2),
// isMobile ? const Spacer() : const SizedBox(width: 15),
Icon(Icons.file_download_outlined, size: 16, color: Colors.white),
],
),
),
);
}
Future<void> createUserData(Map<String, dynamic> userData) async {
final bool isNewRow =
idPrimary != null &&
int.tryParse(idPrimary.toString()) != null &&
int.parse(idPrimary.toString()) > 1000000000000;
final bool isUpdating =
idPrimary != null && idPrimary != 'null' && !isNewRow;
print('isNewRow: $isNewRow, isUpdating: $isUpdating');
// final bool isUpdating = idPrimary != 'null';
final id = idPrimary;
print('UPDId- $id');
// final bool isUpdating = false;
final uri = Uri.parse(
isUpdating
? '${Env.apiUrl}enquiry/updateEnquiry'
: '${Env.apiUrl}enquiry/createEnquiry',
);
if (_token == null) {
throw Exception('Token not found. Please log in.');
}
// Use MultipartRequest (POST only)
final request = http.MultipartRequest('POST', uri);
request.headers['Authorization'] = 'Bearer $_token';
request.headers['app-signature'] = Env.App_Signature;
// If updating, spoof the method Laravel-style
if (isUpdating) {
request.fields['id'] = id!;
request.fields['updated_by'] = userId!.toString();
} else {
request.fields['created_by'] = userId!.toString();
}
print("USerDAta - $userData");
// userData.forEach((key, value) {
// request.fields[key] = value.toString();
// print("✅ Encoded travel_details2: ${request.fields[key]}");
// });
await attachFiles(request);
userData.forEach((key, value) {
if (key != 'rc_file_name' && key != 'id_proof_file_name') {
request.fields[key] = value.toString();
print("✅ Encoded $key: ${request.fields[key]}");
} else {
print('Something Missing..');
}
});
// attach files
// request.fields['agent_id'] = selectedId.toString();
print(" Sending request with fields: ${request.fields}");
try {
final streamedResponse = await request.send();
final response = await http.Response.fromStream(streamedResponse);
print("Response status: ${response.statusCode}");
print("Response body: ${response.body}");
if (response.statusCode == 200 || response.statusCode == 201) {
// dispose();
print("✅ Enquiry submitted successfully!");
ToastHelper.showSuccessToast(context, 'Saved Enquiry');
print("Response: ${response.body}");
// Clear the temporary ID
idPrimary = null;
refrshfilterDateRange();
// getStaffList(managerId, roleId);
// setState(() => isSaving = false);
} else if (response.statusCode == 403) {
await apiService.clearLocalStorageAndRedirect();
} else {
print("❌ Submission failed. Status: ${response.statusCode}");
print("Body: ${response.body}");
// setState(() => isSaving = false);
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Enquiry Creation Failed"),
content: Text(
"There was a problem in creating enquiry. Please try again.",
),
actions: [
TextButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
} catch (e) {
print("🔥 Error submitting user: $e");
}
}
Future<void> uploadPolicyPDF({
required Map<String, dynamic> data,
required PlatformFile? file,
}) async {
if (_token == null) throw Exception("Token missing. Please login.");
final uri = Uri.parse('${Env.apiUrl}policy/uploadPolicyFile');
final request = http.MultipartRequest('POST', uri);
request.headers['Authorization'] = 'Bearer $_token';
request.headers['app-signature'] = Env.App_Signature;
print("🔥 Upload Request Data: $data");
// 1⃣ Add text fields (except file)
data.forEach((key, value) {
if (value != null && key != 'policy_pdf_file_name') {
request.fields[key] = value.toString();
print("➡️ Field Added: $key = ${request.fields[key]}");
}
});
// 2⃣ Attach file
if (file != null) {
if (file.bytes != null) {
request.files.add(
http.MultipartFile.fromBytes(
'policy_pdf_file_name',
file.bytes!,
filename: file.name,
),
);
} else if (file.path != null) {
request.files.add(
await http.MultipartFile.fromPath(
'policy_pdf_file_name',
file.path!,
filename: file.name,
),
);
}
print("📎 File attached: ${file.name}");
} else {
print("⚠️ No file selected!");
}
// 3⃣ Submit request
try {
final response = await http.Response.fromStream(await request.send());
print("📥 Response Code: ${response.statusCode}");
print("📥 Response Body: ${response.body}");
if (response.statusCode == 200 || response.statusCode == 201) {
ToastHelper.showSuccessToast(context, "Policy PDF Uploaded!");
refrshfilterDateRange();
return;
}
if (response.statusCode == 403) {
await apiService.clearLocalStorageAndRedirect();
return;
}
ToastHelper.showErrorToast(context, "Upload failed!");
} catch (e) {
print("❌ Error uploading file: $e");
}
}
//---------------------- Enquiry TAB Logins End -------------------------------- //
@override
Widget build(BuildContext context) {
return MainLayout(
title: "Enquiries",
body: Container(
// color: Colors.yellow.shade50,
// color: Color(0xFFFCFCFC),
// color: Colors.white,
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(12),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.08),
// blurRadius: 8,
// spreadRadius: 1,
// offset: const Offset(0, 2), // soft drop shadow
// ),
// ],
// ),
width: MediaQuery.of(context).size.width,
// height: MediaQuery.of(context).size.height * 0.1,
// padding: EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
// height: MediaQuery.of(context).size.height*0.
child: RaiseEnqForm(
key: raiseEnqKey,
onSubmit: (dataSet) async {
print('Check checking dataSet $dataSet');
try {
await createUserData(dataSet); // <-- API call here
print("Enquiry saved successfully");
// RESET FORM
raiseEnqKey.currentState?.resetFormFields();
} catch (e) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Error'),
content: Text(e.toString()),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('OK'),
),
],
),
);
}
},
),
),
// SizedBox(height: 5),
ResponsiveLayout.isMobile(context)
? Container(
height: MediaQuery.of(context).size.height * 0.69,
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(2),
child: _buildContent(context),
),
),
)
:
// Expanded(
// child:
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.69,
// padding: EdgeInsets.symmetric(
// horizontal: 8.0,
// vertical: 2.0,
// ),
child: _buildContent(context),
),
// ),
// Container(
// // height: 20,
// width: MediaQuery.of(context).size.width,
// // color: Colors.green.shade50,
// child: PaginationControls(
// currentPage: currentPage,
// itemsPerPage: itemsPerPage,
// // totalItems: dataVal.length,
// totalItems: filteredData.length,
// // activeColor: layoutColor, // your theme color
// onPageChanged: (page) {
// setState(() {
// currentPage = page;
// });
// },
// onItemsPerPageChanged: (items) {
// setState(() {
// itemsPerPage = items;
// currentPage = 1;
// });
// },
// ),
// ),
],
),
),
);
}
Future<void> buildStatusActions(BuildContext context, status, id) async {
print("Actionsstatus - $status -$id");
dynamic val;
if (status == 'Awaiting Proposal' || status == 'Proposal Created') {
val = '';
} else {
val = 'Policy';
}
final result = await showDialog(
context: context,
barrierDismissible:
false, // optional - prevents closing by tapping outside
builder: (context) => TabEnquiryStaffList(showKey: val),
);
// ✅ Code here runs *after* the dialog is closed
print("Dialog closed");
print("Dialog result: $result");
refrshfilterDateRange();
}
Future<void> buildPolicyCreatedStatusActions(
BuildContext context,
status,
id,
) async {
print("Actionsstatus - $status -$id");
dynamic val;
if (status == 'Awaiting Proposal' || status == 'Proposal Created') {
val = '';
} else {
val = 'Policy';
}
final result = await showDialog(
context: context,
barrierDismissible:
false, // optional - prevents closing by tapping outside
builder: (context) => PolicyStaffEnqList(),
);
// ✅ Code here runs *after* the dialog is closed
print("Dialog closed");
print("Dialog result: $result");
if (result) {
setState(() {
isActionable = false;
});
}
refrshfilterDateRange();
}
Widget _buildContent(BuildContext context) {
final isDesktop = !ResponsiveLayout.isMobile(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 10),
Container(
// height: 40,
// color: Color(0xffF1F5F9),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (_showFilterRow) ...[
DateFilterRow(
key: ValueKey(SelectedStatus ?? ''),
role: roleId,
id: userId,
selectedStatusVal: SelectedStatus,
onFilterStaff: (val) {
print('Selected Filterd STAFF Id - $val');
SelectedStaffId = val;
},
selectedStaffId: SelectedStaffId,
startController: controllers['startDate']!,
endController: controllers['endDate']!,
onStatusChanged: (val) {
SelectedStatus = val; // update parent
},
formKey: _formKey,
isMobile: ResponsiveLayout.isMobile(context),
onFilter: () {
// call your filter logic
filterDateRange();
},
onRefresh: () {
// call your refresh logic
refrshfilterDateRange();
},
),
],
IconButton(
icon: Icon(
_showFilterRow
? Icons.close_fullscreen
: Icons.filter_alt_outlined,
),
iconSize: 18,
color: const Color(0xFF94A3B8),
tooltip: _showFilterRow ? 'Hide Filters' : 'Show Filters',
onPressed: () {
setState(() {
_showFilterRow = !_showFilterRow;
});
},
),
ThemedSearchField(
hintText: 'Search',
backgroundColor: Color(0xFFFFFFFF),
txtHeight: 30,
// backgroundColor: Colors.white,
onChanged: filterData,
controller: _searchStaffController,
txtwidth: ResponsiveLayout.isMobile(context)
? MediaQuery.of(context).size.width * 0.7
: MediaQuery.of(context).size.width * 0.13,
),
SizedBox(width: 10),
ExportBtn(
sheetName: "Enquiry",
fileName: "Enquiry_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context) ? true : false,
displayHeaders: [
"Received Date & Time",
"Partner",
"Assigned To",
"Insurer",
"Vehicle.No.",
"Insured Name",
"Assigned Date & Time",
"Premium",
"Payment Mode",
"Policy Number",
"Status",
],
keys: [
"created_on",
"agent_name",
"assigned_to_name",
"insurer_name",
"reg_no",
"insured_name",
"updated_on",
"premium_amount",
"payment_mode",
"policy_number",
"status",
],
),
],
),
),
SizedBox(height: 5),
ResponsiveLayout.isMobile(context)
? _buildDataTable(context)
:
// Expanded(
// child:
Container(
height: MediaQuery.of(context).size.height * 0.55,
decoration: BoxDecoration(color: Colors.white),
child: _buildDataTable(context),
),
// ),
],
);
}
// 1. Add group expansion state tracking
Map<String, bool> groupExpanded = {
// 'Awaiting Proposal': true,
// 'Proposal Created': true,
// 'Proposal Accepted': true,
// 'Proposal Rejected': true,
'Awaiting Payment': true,
'Policy Created': true,
};
Map<String, String> statusGroupMap = {
// 'Awaiting Proposal': 'Awaiting Proposal',
// 'Proposal Created': 'Proposal Created',
// 'Proposal Accepted': 'Proposal Created',
// 'Proposal Rejected': 'Proposal Created',
'Proposal Created': 'Awaiting Payment',
'Proposal Accepted': 'Awaiting Payment',
'Proposal Rejected': 'Awaiting Payment',
'Policy Created': 'Policy Created',
};
// Add group enquiries by status
Map<String, List<Map<String, dynamic>>> get _groupedEnquiries {
final grouped = <String, List<Map<String, dynamic>>>{};
// Initialize all status groups
// grouped['Awaiting Proposal'] = [];
// grouped['Proposal Created'] = [];
// grouped['Proposal Accepted'] = [];
// grouped['Proposal Rejected'] = [];
// grouped['Policy Created'] = [];
grouped['Awaiting Payment'] = [];
// Initialize only the groups you need
// grouped['Awaiting Proposal'] = [];
// grouped['Proposal Created'] = [];
grouped['Policy Created'] = [];
// Group the filtered/paginated data by status
// for (var item in _paginatedData) {
// final status = item['status']?.toString() ?? 'Unknown';
// if (grouped.containsKey(status)) {
// grouped[status]!.add(item);
// }
// }
for (var item in filteredData) {
final status = item['status']?.toString() ?? 'Unknown';
// Map actual status to grouped status
final groupKey = statusGroupMap[status];
if (groupKey != null) {
grouped[groupKey]!.add(item);
}
}
// for (var item in _paginatedData) {
// final status = item['status']?.toString() ?? 'Unknown';
//
// // Map actual status to grouped status
// final groupKey = statusGroupMap[status];
//
// if (groupKey != null) {
// grouped[groupKey]!.add(item);
// }
// }
return grouped;
}
Map<String, dynamic> _getStatusConfig(String status) {
switch (status) {
case 'Awaiting Proposal':
return {'icon': '🕐', 'color': Color(0xFFfff3cd)};
case 'Proposal Created':
return {'icon': '📝', 'color': Color(0xFFd1ecf1)};
// case 'Proposal Accepted':
// return {'icon': '✅', 'color': Color(0xFFd4edda)};
// case 'Proposal Rejected':
// return {'icon': '❌', 'color': Color(0xFFf8d7da)};
case 'Policy Created':
return {'icon': '📋', 'color': Color(0xFFcfe2ff)};
default:
return {'icon': '📄', 'color': Color(0xFFe9ecef)};
}
}
final Map<String, Color> statusColors = {
'Awaiting Proposal': const Color(0xFFEFF6FF), // Blue 50
'Proposal Created': const Color(0xFFF0F9FF), // Cyan 50
'Proposal Accepted': const Color(0xFFF0FDF4), // Green 50
'Proposal Rejected': const Color(0xFFFEF9C3), // Yellow 50
'Policy Created': const Color(0xFFF3F0FF), // Purple 50 (generated)
};
final Map<String, Color> statusBorderColors = {
'Awaiting Proposal': const Color(0xFFDBEAFE), // Blue 100
'Proposal Created': const Color(0xFFBAE6FD), // Cyan 100
'Proposal Accepted': const Color(0xFFDCFCE7), // Green 100
'Proposal Rejected': const Color(0xFFFEF3C7), // Yellow 100
'Policy Created': const Color(0xFFF0E4FF), // Purple 200
// 'Policy Created': const Color(0xFFE5CCFF), // Purple 200
};
final Map<String, Color> statusTextColors = {
'Awaiting Proposal': const Color(0xFF1D4ED8), // Blue 700
'Proposal Created': const Color(0xFF0369A1), // Cyan 700
'Proposal Accepted': const Color(0xFF15803D), // Green 700
'Proposal Rejected': const Color(0xFFB45309), // Amber 700
'Policy Created': const Color(0xFF6B21A8), // Purple 700
};
// 4. Build group header widget
Widget _buildGroupHeader(String status, int count) {
final config = _getStatusConfig(status);
final isExpanded = groupExpanded[status] ?? true;
return InkWell(
onTap: () {
setState(() {
groupExpanded[status] = !isExpanded;
});
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: Row(
children: [
Icon(
isExpanded ? Icons.arrow_drop_down : Icons.arrow_right,
size: 22,
color: Color(0xFF5A6C7D),
),
SizedBox(width: 8),
Text(
// '${config['icon']} $status',
'$status',
style: GoogleFonts.inter(
fontSize: 13,
color: Colors.black,
// color: Colors.orange,
fontWeight: FontWeight.w600,
),
),
// Spacer(),
SizedBox(width: 8),
Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2),
decoration: BoxDecoration(
color: Color(0xFFEFF6FF),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'$count',
style: GoogleFonts.inter(
fontSize: 11,
color: Color(0xFF2563EB),
fontWeight: FontWeight.w500,
),
),
),
],
),
),
);
}
Widget _buildDataTable(context) {
final screenWidth = MediaQuery.of(context).size.width;
final isMobile = ResponsiveLayout.isMobile(context);
// Calculate minimum width based on screen size
double minTableWidth = isMobile ? screenWidth * 1.5 : 1300;
return
// Expanded(
// child:
LayoutBuilder(
builder: (context, constraints) {
// double minWidth = constraints.maxWidth < 1300
// ? 1300
// : constraints.maxWidth;
double tableWidth = constraints.maxWidth > minTableWidth
? constraints.maxWidth
: minTableWidth;
final isDesktop = !ResponsiveLayout.isMobile(context);
return ScrollConfiguration(
behavior: const MaterialScrollBehavior().copyWith(
dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch},
),
child: SingleChildScrollView(
controller: _horizontalScrollController, // 👈 Shared controller
scrollDirection: Axis.horizontal,
child: SizedBox(
// width: minWidth,
width: tableWidth,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Fixed Header
Container(
decoration: BoxDecoration(
// color: Colors.white,
color: Color(0xFFF1F5F9),
borderRadius: BorderRadius.circular(5),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.08),
// blurRadius: 8,
// spreadRadius: 1,
// offset: const Offset(0, 2),
// ),
// ],
),
child: _buildTableHeader(isDesktop),
),
// Scrollable Data Content
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
// border: Border.all(color: Colors.grey, width: 0.1),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.08),
// blurRadius: 8,
// spreadRadius: 1,
// offset: const Offset(0, 2),
// ),
// ],
),
child: _buildDataTableContent(), // 👈 New method
),
),
],
),
),
),
);
},
);
// );
}
Widget _buildDataTableContent() {
if (filteredData.isEmpty) {
return const SizedBox(
height: 50,
child: Center(child: Text('No available data')),
);
}
final groupedData = _groupedEnquiries;
return SingleChildScrollView(
scrollDirection: Axis.vertical, // 👈 Only vertical scroll here
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...groupedData.entries.map((entry) {
final status = entry.key;
final items = entry.value;
final isExpanded = groupExpanded[status] ?? true;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Group Header
if (status != 'Proposal Accepted' &&
status != 'Proposal Rejected') ...[
Container(
// width: double.infinity,
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.fromLTRB(12, 4, 12, 4),
child: _buildGroupHeader(status, items.length),
),
],
// Group rows
if (isExpanded && items.isNotEmpty)
...items.asMap().entries.map((itemEntry) {
final index = itemEntry.key;
final item = itemEntry.value;
final startIndex = (currentPage - 1) * itemsPerPage;
final sno = startIndex + index + 1;
final id = int.tryParse(item['id'].toString()) ?? 0;
final selectId = item['id'].toString() ?? '0';
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Data row
Container(
width: MediaQuery.of(
context,
).size.width, // margin: EdgeInsets.all(10.0),
margin: EdgeInsets.fromLTRB(12, 8, 12, 8),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Color(0xFFFFECF1F7)),
// border: Border(
// left: BorderSide(color: Color(0XFFE2E8F0)),
// right: BorderSide(color: Color(0XFFE2E8F0)),
// bottom: BorderSide(color: Color(0XFFE2E8F0)),
// top: BorderSide(color: Color(0XFFE2E8F0)),
// ),
borderRadius: BorderRadius.circular(8),
// boxShadow: [
// BoxShadow(
// color: Color(0XFFE2E8F0),
// blurRadius: 3,
// spreadRadius: 0.2,
// offset: const Offset(0, 1), // soft drop shadow
// ),
// ],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
DataTable(
border: TableBorder.all(
color: Colors.transparent,
width: 0,
),
// border: TableBorder(
// top: BorderSide.none,
// left: BorderSide.none,
// right: BorderSide.none,
// bottom: BorderSide.none,
// horizontalInside: BorderSide.none,
// verticalInside: BorderSide.none,
// ),
headingRowHeight: 0,
dividerThickness: 0.1,
columnSpacing: 20.0,
dataRowMinHeight: 40,
columns: _buildDataColumns(),
rows: [_buildDataRow(item, sno)],
),
// Expanded content
if (rowExpanded[id] ?? false)
Container(
// width: double.infinity,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
// borderRadius: const BorderRadius.only(
// topLeft: Radius.circular(25),
// topRight: Radius.circular(25),
// ),
border: Border.all(
color: Color(0xFFC7D2FE),
),
color: Color(0xFFF8F9FF),
),
padding: const EdgeInsets.symmetric(
vertical: 14.0,
horizontal: 8.0,
),
child: _buildSubRow(context, selectId, id),
),
],
),
),
],
);
}).toList(),
if (isExpanded && items.isEmpty)
if (status != 'Proposal Accepted' &&
status != 'Proposal Rejected')
Padding(
padding: EdgeInsets.all(5),
child: Center(
child: Text(
'No enquiries found',
style: GoogleFonts.inter(
fontSize: 12,
color: Colors.grey,
fontStyle: FontStyle.italic,
),
),
),
),
],
);
}).toList(),
],
),
);
}
DataRow _buildDataRow(Map<String, dynamic> item, int sno) {
final id = int.tryParse(item['id'].toString()) ?? 0;
final selectId = item['id'].toString() ?? '0';
final bool isNewRow =
int.tryParse(id.toString()) != null &&
int.parse(id.toString()) > 1000000000000;
print('idPrimary - $idPrimary');
print('isNewRow - $isNewRow');
final editing = isEditingRow[id] ?? false;
final controllers = rowControllers[id];
String status = item['status'] ?? '-';
Color bgColor = statusColors[status] ?? Color(0xFFF9FAFB);
Color borderColor = statusBorderColors[status] ?? Color(0xFFE2E8F0);
return DataRow(
cells: [
DataCell(
SizedBox(
width: 70,
child: Row(
children: [
// Text(item['created_on'], style: _dataBold),
Text(
formatDateTimeForTable(item['created_on'], newLine: true),
style: _dataBoldthm1,
),
],
),
),
),
// Assigned To
DataCell(
SizedBox(
// flex: 1,
width: 110,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item['assigned_to_name'] != null
? _truncateText(item['assigned_to_name'], 15)
: '-',
style: _dataBold,
),
Text(_formatDate(item['updated_on']), style: _dataBoldthm4),
],
),
),
),
// Agent Name
DataCell(
SizedBox(
width: 100,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(item['agent_code'] ?? '-', style: _dataBold),
Text(
item['agent_name'] != null
? _truncateText(item['agent_name'], 20)
: '-',
style: _tableDataTimeStyle,
),
],
),
),
),
// Insurer Name
DataCell(
SizedBox(
width: 60,
child: Text(
item['insurer_short_name'] ?? '-',
style: _dataBoldthm2,
),
),
),
DataCell(
SizedBox(
width: 90,
child: Tooltip(
message: item['insured_name'],
child: Text(
item['insured_name'] != null
? _truncateText(item['insured_name'], 15)
: '-',
style: _dataBoldthm2,
),
),
),
),
// Vehicle No
DataCell(
SizedBox(
width: 95,
child: Builder(
builder: (buttonContext) => Material(
color: Colors.white,
// child: InkWell(
// // hoverColor: Color(0xFFEAF6F4),
// onTap: () async {
// // ✅ Get overlay and button position RELATIVE to InkWell
// final RenderBox button =
// buttonContext.findRenderObject() as RenderBox;
// final RenderBox overlay =
// Overlay.of(buttonContext).context.findRenderObject()
// as RenderBox;
//
// final Offset position = button.localToGlobal(
// Offset.zero,
// ancestor: overlay,
// );
//
// await showMenu(
// context: buttonContext,
// position: RelativeRect.fromLTRB(
// position.dx,
// position.dy + button.size.height,
// overlay.size.width,
// // overlay.size.width - position.dx,
// 0,
// ),
// items: [
// PopupMenuItem(
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: _buildPopupDownload_BTN(
// buttonContext,
// item,
// item['id'],
// item['reg_no'],
// ),
// ),
// ),
// ],
// color: Colors.white,
// );
// },
child: Tooltip(
message:
'Click and Download Enquiry Files', // what appears on hover/long press
waitDuration: const Duration(milliseconds: 500), // optional
showDuration: const Duration(seconds: 2), // optional
child: Container(
padding: EdgeInsets.all(4.0),
margin: EdgeInsets.symmetric(vertical: 3, horizontal: 2),
decoration: BoxDecoration(
color: Color(0xFFF9FAFB),
border: Border.all(color: Color(0xFFE2E8F0)),
borderRadius: BorderRadius.circular(5),
),
child: Text(item['reg_no'] ?? '-', style: _dataBoldthm3),
),
),
// child: Text(item['reg_no'] ?? '-', style: _dataBold),
// ),
),
),
),
),
DataCell(
SizedBox(
width: 70.0,
child: Row(
children: [
Tooltip(
richMessage: WidgetSpan(
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
// color: Colors.white,
color: Color(0xFFFFFCF2),
borderRadius: BorderRadius.circular(6),
// border: Border.all(color: Colors.blueGrey.shade100),
// border: Border.all(color: Colors.grey.shade400),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisSize: MainAxisSize.min,
children: [
_row("Email", item["email"]),
_row("Mobile", item["mobile"]),
_row("Remarks", item["remarks"]),
],
),
),
),
padding: EdgeInsets
.zero, // important (remove default tooltip padding)
verticalOffset: 12, // distance from icon
waitDuration: Duration(milliseconds: 500),
showDuration: Duration(seconds: 2),
decoration: BoxDecoration(), // remove default yellow box
child: Icon(
Icons.info_outline_rounded,
size: 15,
color: Colors.blue,
),
),
IconButton(
padding: EdgeInsets.zero, // remove outer padding
constraints: BoxConstraints(
// reduce clickable area
minWidth: 24,
minHeight: 24,
),
icon: Icon(
rowExpanded[id] == true ? Icons.edit_off_sharp : Icons.edit,
color: Colors.blue,
size: 15,
),
tooltip: "Edit",
onPressed: () {
setState(() {
isActionable = false;
bool isOpen = rowExpanded[id] ?? false;
rowExpanded.clear(); // close all
if (!isOpen) {
isActionable = true;
rowExpanded[id] =
true; // open only if previously closed
}
// rowExpanded[id] = !(rowExpanded[id] ?? false);
});
handleEdit(item);
},
),
Builder(
builder: (buttonContext) => Material(
color: Colors.white,
child: InkWell(
// hoverColor: Color(0xFFEAF6F4),
onTap: () async {
// ✅ Get overlay and button position RELATIVE to InkWell
final RenderBox button =
buttonContext.findRenderObject() as RenderBox;
final RenderBox overlay =
Overlay.of(buttonContext).context.findRenderObject()
as RenderBox;
final Offset position = button.localToGlobal(
Offset.zero,
ancestor: overlay,
);
await showMenu(
context: buttonContext,
position: RelativeRect.fromLTRB(
position.dx,
position.dy + button.size.height,
overlay.size.width,
// overlay.size.width - position.dx,
0,
),
items: [
PopupMenuItem(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [MultipleFileWidget(enqId: selectId)],
),
),
],
color: Colors.white,
);
},
child: Tooltip(
message:
'Click and Upload / Download Enquiry Files', // what appears on hover/long press
waitDuration: const Duration(
milliseconds: 500,
), // optional
showDuration: const Duration(seconds: 2), // optional
child: Icon(
Icons.file_upload_outlined,
size: 15,
color: Colors.green,
),
),
),
),
),
],
),
),
),
// Assigned Date (formatted)
// DataCell(
// SizedBox(
// width: 80,
// child: Text(
// // item['updated_on'] ?? '-',
// formatDateTimeForTable(item['updated_on'], newLine: true),
// style: _dataBold,
// softWrap: true,
// maxLines: 3,
// ),
// ),
// ),
// Premium
// DataCell(
// // (item['status'] == 'Proposal Created' ||
// // item['status'] == 'Proposal Rejected')
// // ? SizedBox(
// // width: 80,
// // child: InkWell(
// // onTap: () {
// // // handle approval logic
// //
// // handleProposalAccept(context, item['id']);
// // },
// // child: Text(
// // 'Click To\nApprove',
// // style: GoogleFonts.inter(
// // color: Colors.green,
// // fontSize: 11,
// // fontWeight: FontWeight.w700,
// // ),
// // ),
// // ),
// // )
// // :
// SizedBox(
// width: 90,
// child: Text(
// item['premium_amount']?.toString() ?? '-',
// style: _dataBold,
// ),
// ),
// ),
// // Payment Mode
// DataCell(
// // Material(child: buildPaymentMode(context)),
// SizedBox(
// // width: 80,
// // child: Text(item['payment_mode_value'] ?? '-', style: _dataBold),
// child: buildPaymentMode(context),
// ),
// ),
DataCell(
SizedBox(
height: 30,
width: 120,
child: (item['status'] == 'Awaiting Proposal')
? Text('-')
: Builder(
builder: (cellContext) =>
DropdownSearch<Map<String, dynamic>>(
key: ValueKey(item['id']),
selectedItem: () {
try {
return filteredPaymentModeData.firstWhere(
(mode) =>
mode['id'].toString() ==
item['payment_mode_id'].toString(),
orElse: () => {},
);
} catch (e) {
return null;
}
}(),
items: (filter, infiniteScrollProps) async {
if (filter.isEmpty) {
return filteredPaymentModeData;
}
return filteredPaymentModeData.where((paymentMode) {
return paymentMode['value']
.toString()
.toLowerCase()
.contains(filter.toLowerCase());
}).toList();
},
itemAsString: (val) => val['value'].toString(),
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'],
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
isVisible: true,
padding:
EdgeInsets.zero, // remove default padding
constraints: const BoxConstraints(
// shrink icon tap area
minWidth: 12,
minHeight: 12,
),
iconSize: 8, // smaller icon
color: Colors.black45,
// icon: const Icon(Icons.arrow_drop_down),
),
),
dropdownBuilder: (context, selectedItem) {
// Check if selectedItem is null or empty map
bool isEmpty =
selectedItem == null ||
selectedItem.isEmpty ||
selectedItem['value'] == null;
return Container(
padding: EdgeInsets.zero,
// padding: EdgeInsets.symmetric(horizontal: 2),
alignment: Alignment.centerLeft,
child: Text(
isEmpty
? "Select Payment Mode"
: selectedItem['value'].toString(),
style: GoogleFonts.poppins(
fontSize: 11,
color: isEmpty
? Colors
.grey
.shade500 // Grey for hint
: Colors.black, // Blue for selected
// : Color(0XFF6366F1), // Blue for selected
fontWeight: isEmpty
? FontWeight.w400
: FontWeight.w400,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
);
},
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
// label: "Select Payment Mode",
).copyWith(
filled: true,
fillColor: Colors
.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0xFFE2E8F0),
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
//color: Color(0xFFFFFFFF),
color: Color(0xFFE2E8F0),
width: 0.5,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
vertical: 6,
),
),
),
// 🔥 Use PopupProps.menu - works better in tables
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(
maxHeight: 300,
minWidth: 200,
),
menuProps: MenuProps(
backgroundColor: Colors.white,
elevation: 8,
borderRadius: BorderRadius.circular(8),
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
autofocus: true,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(8),
filled: true,
fillColor: Color(0xFFF8FAFC),
hintText: "Search...",
hintStyle: GoogleFonts.inter(
fontSize: 11,
color: Colors.grey,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(
color: Color(0xFFE2E8F0),
),
),
),
),
itemBuilder:
(context, item, isDisabled, isSelected) {
return Container(
color: isSelected
? Colors.blue.withOpacity(0.1)
: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 5,
vertical: 5,
),
child: Text(
item['value'].toString(),
style: GoogleFonts.inter(
fontSize: 11,
color: Colors.black87,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.normal,
),
),
);
},
),
onChanged: (val) {
dynamic id;
if (val != null) {
setState(() {
item['payment_mode_id'] = val['id'].toString();
item['payment_mode'] = val['value'].toString();
id = val['id'].toString();
});
}
handlePaymentMode(id, item['quotation_id']);
},
),
),
),
),
DataCell(
SizedBox(
width: 120,
height: 30,
child: (item['status'] == 'Awaiting Proposal')
? Text('-')
: Builder(
builder: (cellContext) => DropdownSearch<Map<String, dynamic>>(
key: ValueKey(item['id']),
selectedItem: () {
try {
return filteredInsuranceData.firstWhere(
(mode) =>
mode['insurance_plan_type']
.toString()
.trim()
.toLowerCase() ==
item['insurance_plan_type']
.toString()
.trim()
.toLowerCase(),
orElse: () => {},
);
} catch (e) {
return null;
}
}(),
items: (filter, infiniteScrollProps) async {
if (filter.isEmpty) {
return filteredInsuranceData;
}
return filteredInsuranceData.where((paymentMode) {
return paymentMode['insurance_plan_type']
.toString()
.toLowerCase()
.contains(filter.toLowerCase());
}).toList();
},
itemAsString: (val) =>
val['insurance_plan_type'].toString(),
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'],
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
isVisible: true,
padding: EdgeInsets.zero, // remove default padding
constraints: const BoxConstraints(
// shrink icon tap area
minWidth: 12,
minHeight: 12,
),
iconSize: 8, // smaller icon
color: Colors.black45,
// icon: const Icon(Icons.arrow_drop_down),
),
),
dropdownBuilder: (context, selectedItem) {
// Check if selectedItem is null or empty map
bool isEmpty =
selectedItem == null ||
selectedItem.isEmpty ||
selectedItem['insurance_plan_type'] == null;
return Container(
padding: EdgeInsets.zero,
// padding: EdgeInsets.symmetric(horizontal: 2),
alignment: Alignment.centerLeft,
child: Text(
isEmpty
? "Select Insurer"
: selectedItem['insurance_plan_type']
.toString(),
style: GoogleFonts.poppins(
fontSize: 11,
color: isEmpty
? Colors
.grey
.shade500 // Grey for hint
: Colors.black, // Blue for selected
// : Color(0XFF6366F1), // Blue for selected
fontWeight: isEmpty
? FontWeight.w400
: FontWeight.w400,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
);
},
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
// label: "Select Payment Mode",
).copyWith(
filled: true,
fillColor: Colors
.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0xFFE2E8F0),
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
//color: Color(0xFFFFFFFF),
color: Color(0xFFE2E8F0),
width: 0.5,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
vertical: 6,
),
),
),
// 🔥 Use PopupProps.menu - works better in tables
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(
maxHeight: 300,
minWidth: 200,
),
menuProps: MenuProps(
backgroundColor: Colors.white,
elevation: 8,
borderRadius: BorderRadius.circular(8),
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
autofocus: true,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(8),
filled: true,
fillColor: Color(0xFFF8FAFC),
hintText: "Search...",
hintStyle: GoogleFonts.inter(
fontSize: 11,
color: Colors.grey,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: Color(0xFFE2E8F0)),
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
color: isSelected
? Colors.blue.withOpacity(0.1)
: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 5,
vertical: 5,
),
child: Text(
item['insurance_plan_type'].toString(),
style: GoogleFonts.inter(
fontSize: 11,
color: Colors.black87,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.normal,
),
),
);
},
),
onChanged: (val) {
dynamic id;
if (val != null) {
setState(() {
// item['payment_mode_id'] = val['id'].toString();
item['insurance_plan_type'] =
val['insurance_plan_type'].toString();
id = val['id'].toString();
});
}
handleInsurancePlanTypeMode(id, item['quotation_id']);
},
),
),
),
),
// DataCell(
// buildPaymentMode(context),
// // Builder(
// // builder: (cellContext) => buildPaymentMode(cellContext),
// // // SizedBox(width: 200, child: buildPaymentMode(cellContext)),
// // ),
// ),
// Policy Number
DataCell(
SizedBox(
height: 30,
width: 90,
child: (item['status'] == 'Awaiting Proposal')
? Text('-')
: ThemedUploadField(
// hintText: item['policy_pdf_file_name'] ?? "Upload Document",
hintText:
selectedFileNames ??
item['policy_pdf_file_name'] ??
"Upload Document",
// txtheight: 30,
padHorizontal: 4,
padVertical: 5,
fontSZ: 11,
borderCirculr: 5,
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.18,
borderColor: Color(0xFFE2E8F0),
// highlightColor: Color(0xFF50A398),
// backgroundColor: Color(0xFFEDF6F5),
onFileSelected: (fileName, file) async {
print("Picked file: $fileName (${file.size} bytes)");
if (!fileName.toLowerCase().endsWith('.pdf')) {
ToastHelper.showErrorToast(
context,
'Only PDF files are allowed.',
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Only PDF files are allowed.'),
backgroundColor: Colors.red,
),
);
return; // Stop here
}
print("Picked file: $fileName (${file.size} bytes)");
setState(() {
docUploadedFile = file;
});
await uploadPolicyPDF(
file: docUploadedFile,
data: {
"quotation_id": item["quotation_id"],
"manager_id": managerId,
"created_by": userId,
"updated_by": userId,
// "id": item["id"], // ONLY while updating
},
);
// if (selectedBrokerName == 'Nhance') {
// handleUploadPolicy();
// }
},
),
),
),
DataCell(
SizedBox(
width: 120,
child: Material(
color: Colors.white,
child: InkWell(
// hoverColor: Color(0xFFEAF6F4),
// onTap: null,
onTap: () async {
setState(() {
isActionable = true;
});
buildPolicyCreatedStatusActions(
context,
item['status'],
item['id'],
);
},
child: Tooltip(
message: 'Click to View or Process Enquiry', //
waitDuration: const Duration(milliseconds: 500), // optional
showDuration: const Duration(seconds: 2), // optional
child: Container(
padding: EdgeInsets.symmetric(vertical: 2.0),
margin: EdgeInsets.symmetric(vertical: 2),
decoration: BoxDecoration(
// color: Color(0xFFF9FAFB),
// border: Border.all(color: Color(0xFFE2E8F0)),
color: bgColor,
border: Border.all(color: borderColor),
borderRadius: BorderRadius.circular(5),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item['status'] ?? '-',
style: GoogleFonts.inter(
fontSize: 10,
color: Color(0XFF1e293b),
// color: statusTextColors[status] ?? Colors.black,
fontWeight: FontWeight.w500,
),
// overflow: TextOverflow.clip, //
),
],
),
),
),
// child: Text(item['status'] ?? '-', style: _dataBold),
),
),
),
),
// DataCell(
// SizedBox(
// width: 120,
//
// child: Material(
// color: Colors.white,
// child: InkWell(
// // hoverColor: Color(0xFFEAF6F4),
// onTap: () async {
// var status = item['status'];
// var enqId = item['id'];
//
// final prefs = await SharedPreferences.getInstance();
//
// // ✅ Remove old value (if any)
// await prefs.remove('enqStaffDataId');
//
// // ✅ Save the new id
// await prefs.setString('enqStaffDataId', enqId.toString());
// ref.read(quotationStaffIdProvider.notifier).state = enqId;
// buildStatusActions(context, status, enqId);
// },
// child: Tooltip(
// message: 'Click to View or Process Enquiry', //
// waitDuration: const Duration(milliseconds: 500), // optional
// showDuration: const Duration(seconds: 2), // optional
// child: Container(
// padding: EdgeInsets.symmetric(vertical: 2.0),
// margin: EdgeInsets.symmetric(vertical: 2),
// decoration: BoxDecoration(
// // color: Color(0xFFF9FAFB),
// // border: Border.all(color: Color(0xFFE2E8F0)),
// color: bgColor,
// border: Border.all(color: borderColor),
// borderRadius: BorderRadius.circular(5),
// ),
//
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Text(
// item['status'] ?? '-',
// style: GoogleFonts.inter(
// fontSize: 10,
// color: Color(0XFF1e293b),
// // color: statusTextColors[status] ?? Colors.black,
// fontWeight: FontWeight.w500,
// ),
// // overflow: TextOverflow.clip, //
// ),
// ],
// ),
// ),
// ),
// // child: Text(item['status'] ?? '-', style: _dataBold),
// ),
// ),
// ),
// ),
],
);
}
// Add this helper method to generate DataColumns
List<DataColumn> _buildDataColumns() {
return [
DataColumn(label: Text('Received Date', style: _headerStyle)),
DataColumn(label: Text('Assigned To *', style: _headerStyle)),
DataColumn(label: Text('Partner *', style: _headerStyle)),
// const DataColumn(label: Text('Broker *', style: _headerStyle)),
DataColumn(label: Text('Insurer *', style: _headerStyle)),
DataColumn(label: Text('Insured Name *', style: _headerStyle)),
DataColumn(label: Text('Vehicle No *', style: _headerStyle)),
DataColumn(label: Text('Action', style: _headerStyle)),
// const DataColumn(label: Text('Assigned Date', style: _headerStyle)),
// DataColumn(label: Text('Premium', style: _headerStyle)),
DataColumn(label: Text('Payment Mode', style: _headerStyle)),
DataColumn(label: Text('Plan Type', style: _headerStyle)),
DataColumn(label: Text('Policy', style: _headerStyle)),
DataColumn(label: Text('Status', style: _headerStyle)),
];
}
// 6. Build table header
Widget _buildTableHeader(bool isDesktop) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
_buildHeaderCell('Received Date', 110),
_buildHeaderCell('Assigned To *', 130),
_buildHeaderCell('Partner *', 100),
// _buildHeaderCell('Broker *', 100),
_buildHeaderCell('Insurer *', 90),
_buildHeaderCell('Insured Name *', 110),
_buildHeaderCell('Vehicle No *', 90),
_buildHeaderCell('Action', 80),
// _buildHeaderCell('Assigned Date', 100),
// _buildHeaderCell('Premium', 100),
_buildHeaderCell('Payment Mode', 130),
_buildHeaderCell('Plan Type', 130),
_buildHeaderCell('Policy', 120),
_buildHeaderCell('Status', 100),
],
),
);
}
Widget _buildHeaderCell(String label, double width) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 4),
child: SizedBox(
width: width,
child: Text(
label,
style: GoogleFonts.poppins(
fontSize: 11,
fontWeight: FontWeight.w500,
color: Color(0xFF1E293B),
// color: Colors.black87,
),
),
),
);
}
Widget _row(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
mainAxisSize: MainAxisSize.min,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: 70,
child: Text(
"$label ",
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 10,
fontWeight: FontWeight.w500,
),
),
),
SizedBox(
width: 180,
child: Text(
value ?? '-',
softWrap: true,
maxLines: 5,
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 10,
fontWeight: FontWeight.w400,
),
),
),
],
),
);
}
static final _dataBold = GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w500,
color: Colors.black,
// color: Color(0xFF000000),
);
static final _dataBoldthm1 = GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w500,
color: Colors.black,
// color: Color(0xFF000000),
);
static final _dataBoldthm2 = GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w500,
// color: Colors.black,
color: Color(0XFF334155),
);
static final _dataBoldthm3 = GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w500,
// color: Colors.black,
color: Color(0XFF334155),
);
static final _dataBoldthm4 = GoogleFonts.inter(
fontSize: 9,
fontWeight: FontWeight.w500,
// color: Colors.black,
color: Color(0XFF334155),
);
final _tableDataTimeStyle = GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w400,
color: Colors.black,
// color: Color(0XFF94A3B8),
);
final _subLabelTimeStyle = GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w400,
color: Color(0XFF6366F1),
);
static final _dataBoldsmall = GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
);
static final _dataSub = GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w300,
color: Color(0xFF585757),
);
static final _headerStyle = GoogleFonts.inter(
// color: Colors.black,
color: Color(0xFF1E293B),
fontWeight: FontWeight.w500,
);
static final _cardheaderStyle = GoogleFonts.inter(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 12,
);
static final TextStyle _textStyle = GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w600,
);
static const _cardBodyStyle = TextStyle(
color: Color(0xFF545454),
fontWeight: FontWeight.w400,
fontSize: 12,
);
String _truncateText(String text, int limit) {
if (text.length <= limit) return text;
return '${text.substring(0, limit)}...';
}
String _wrapText(String text, int chunkSize) {
if (text.isEmpty) return '-';
final buffer = StringBuffer();
for (int i = 0; i < text.length; i += chunkSize) {
int end = (i + chunkSize < text.length) ? i + chunkSize : text.length;
buffer.writeln(text.substring(i, end)); // adds '\n' after each chunk
}
return buffer.toString().trim();
}
String formatDateTimeForTable(String? input, {bool newLine = false}) {
if (input == null || input.trim().isEmpty) return '-';
final s = input.trim();
DateTime dt;
try {
// Case 1: ISO-like (yyyy-MM-dd ...), DateTime.parse will handle
if (RegExp(r'^\d{4}-\d{2}-\d{2}').hasMatch(s)) {
dt = DateTime.parse(s);
}
// Case 2: day-month-year at start (e.g. 13-11-2025 07:28 AM or 13-11-2025 07:28)
else if (RegExp(r'^\d{2}-\d{2}-\d{4}').hasMatch(s)) {
// Try with am/pm (hh:mm a)
if (RegExp(r'\b(am|pm)\b', caseSensitive: false).hasMatch(s)) {
dt = DateFormat('dd-MM-yyyy hh:mm a').parse(s);
}
// Try 24-hour time "dd-MM-yyyy HH:mm" or just date "dd-MM-yyyy"
else if (RegExp(r'^\d{2}-\d{2}-\d{4}\s+\d{1,2}:\d{2}').hasMatch(s)) {
dt = DateFormat('dd-MM-yyyy HH:mm').parse(s);
} else {
dt = DateFormat('dd-MM-yyyy').parse(s);
}
}
// Case 3: fallback — try parse with DateTime.parse (may throw)
else {
dt = DateTime.parse(s);
}
final dateStr = DateFormat('dd-MM-yyyy').format(dt);
final timeStr = DateFormat('hh:mm a').format(dt); // e.g. 07:28 AM
return newLine ? '$dateStr\n$timeStr' : '$dateStr | $timeStr';
} catch (e) {
// If parsing fails, return original string as fallback (or '-' if empty)
return s.isNotEmpty ? s : '-';
}
}
}