nhance_partner/lib/presentation/screens/handler/enquiryList.dart
2026-08-05 09:53:12 +05:30

3086 lines
102 KiB
Dart

import 'dart:ui';
import 'dart:async';
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:shared_preferences/shared_preferences.dart';
import '../../../../core/routing/routes.dart';
import '../../../../core/services/api_service.dart';
import 'package:universal_html/html.dart' as html;
import '../../../../data/utils/Pagination.dart';
import '../../../core/config/env.dart';
import '../../../data/services/auth_service.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/date_field_theme.dart';
import '../../themes/indicators/search_field_theme.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 '../../widgets/custom_action_popup.dart';
import '../staff/Enquiry/quotationPopUpAccptReject.dart';
import '../staff/assignStaff.dart';
class EnquiryListHandler extends ConsumerStatefulWidget {
const EnquiryListHandler({super.key});
@override
ConsumerState<EnquiryListHandler> createState() => EnquiryHandlerState();
}
class EnquiryHandlerState extends ConsumerState<EnquiryListHandler> {
int currentPage = 1;
int itemsPerPage = 10;
late ApiService apiService;
dynamic userId;
dynamic idPrimary;
dynamic managerId;
dynamic dashboardKey;
dynamic SelectedStatus;
dynamic SelectedStaffId;
dynamic handlerId;
String? _token;
final _formKey = GlobalKey<FormState>();
Map<String, String?> fieldErrors = {};
// 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;
Timer? _staleAssignedBlinkTimer;
bool _isBlinkPhaseOn = false;
Map<String, TextEditingController> controllers = {};
List<String> tabHeader = [
'startDate',
'endDate',
'name',
'email',
'mobile',
'code',
'address',
'regNo',
'remarks',
'enquiry_created_on', // 👈 Add this line
];
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 = [];
String? selectedVehicleType;
String? selectedInsurer;
String? selectedAgent;
PlatformFile? docUploadedRCFile;
PlatformFile? docUploadedIDProof;
PlatformFile? docUploadedPrevPolicy;
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'))
? 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,
"enquiry_created_on" : controllers["enquiry_created_on"]?.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>>>();
//---------------------- 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;
//---------------------- Assign Staff Ends -------------------------------- //
//---------------------- Enquiry TAB Initializations Ends -------------------------------- //
@override
void initState() {
super.initState();
apiService = ApiService();
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();
getBroker();
_staleAssignedBlinkTimer = Timer.periodic(
const Duration(milliseconds: 700),
(_) {
if (!mounted) return;
setState(() {
_isBlinkPhaseOn = !_isBlinkPhaseOn;
});
},
);
}
@override
void dispose() {
_staleAssignedBlinkTimer?.cancel();
for (final controller in controllers.values) {
controller.dispose();
}
for (final rowControlMap in rowControllers.values) {
for (final controller in rowControlMap.values) {
controller.dispose();
}
}
super.dispose();
}
Future<void> _initializeToken() async {
_token = await AuthService.getToken();
print("APISERTOKEN - $_token");
}
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 ?? '',
);
}
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> 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> 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'];
/*
* Some APIs do not return from_date/to_date in the payload.
* Keep UI date fields stable by falling back to the request dates.
*/
final fromDate = (response['from_date'] ?? '').toString().trim().isNotEmpty
? response['from_date'].toString()
: (fromDt?.toString() ?? '');
final toDate = (response['to_date'] ?? '').toString().trim().isNotEmpty
? response['to_date'].toString()
: (toDt?.toString() ?? '');
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['enquiry_created_on']) ?? '-').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,
// width: MediaQuery.of(context).size.width * 0.4,
// height: MediaQuery.of(context).size.height * 0.4,
// width: MediaQuery.of(context).size.width * 0.7,
// height: MediaQuery.of(context).size.height * 0.8,
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'];
// Handle the date formatting for the controller
if (item['enquiry_created_on'] != null) {
DateTime dt = DateTime.parse(item['enquiry_created_on'].toString());
controllers['enquiry_created_on']!.text = DateFormat('dd-MM-yyyy').format(dt);
} else {
controllers['enquiry_created_on']!.text = '';
}
setState(() {});
// setState(() {
// isEditingRow[id] = true;
// rowControllers[id] = {
// 'agent_name': TextEditingController(text: item['agent_name'] ?? ''),
// 'insurer_name': TextEditingController(text: item['insurer_name'] ?? ''),
// 'reg_no': TextEditingController(text: item['reg_no'] ?? ''),
// 'premium_amount': TextEditingController(
// text: item['premium_amount'] ?? '',
// ),
// 'payment_mode': TextEditingController(text: item['payment_mode'] ?? ''),
// };
// });
}
void handleSave(int id) {
// final controllers = rowControllers[id];
// if (controllers == null) return;
if (!_validateRequiredFields()) return;
setState(() {
// final index = getStaffData.indexWhere(
// (e) => e['id'].toString() == id.toString(),
// );
// if (index != -1) {
// getStaffData[index] = {
// ...getStaffData[index],
// 'agent_name': controllers['agent_name']?.text,
// 'insurer_name': controllers['insurer_name']?.text,
// 'reg_no': controllers['reg_no']?.text,
// 'premium_amount': controllers['premium_amount']?.text,
// 'payment_mode': controllers['payment_mode']?.text,
// };
// }
//
// if (index != -1) {
// print("EDITField - ${getStaffData[index]}");
// getStaffData[index] = {
// ...getStaffData[index],
// 'agent_id': selectedAgent,
// "name": controllers["name"]?.text,
// "mobile": controllers["mobile"]?.text,
// "email": controllers["email"]?.text,
// "reg_no": controllers["regNo"]?.text,
// "vehicle_type_id": selectedVehicleType,
// };
// }
isEditingRow[id] = false;
rowControllers.remove(id);
});
handleEnqTabSave();
}
void handleCancel(int id) {
setState(() {
// 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 SizedBox(
height: 35,
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;
},
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;
},
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: Colors.black, width: 0.1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(color: Colors.black, width: 0.1),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 2,
vertical: 6,
),
),
),
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,
),
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: " IO 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) {
Map<String, dynamic>? selectedInsurerd = filteredInsurersData.firstWhere(
(item) => item['id'].toString() == selectedInsurer,
orElse: () => {},
);
return Container(
height: 35,
width: MediaQuery.of(context).size.width * 0.08,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKeyInsurerEnqAsgn,
selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null,
items: (filter, infiniteScrollProps) {
return filteredInsurersData;
},
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;
},
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 Insurer",
).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: 2,
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(
decoration: InputDecoration(
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: 35,
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;
},
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: 2,
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(
decoration: InputDecoration(
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) {
Map<String, dynamic>? selectedAgntName = filteredAgentData.firstWhere(
(item) => item['id'].toString() == selectedAgent,
orElse: () => {},
);
return SizedBox(
width: MediaQuery.of(context).size.width * 0.08,
height: 35,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKeyAgent,
selectedItem: selectedAgntName.isNotEmpty ? selectedAgntName : null,
items: (filter, infiniteScrollProps) {
return filteredAgentData;
},
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;
},
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null
? selectedItem['name'].toString()
: "", // ✅ FIXED
style: GoogleFonts.poppins(fontSize: 11, color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Partner",
).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: 1,
// 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(
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Search Partner...",
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: 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 buildEnquiryCreatedOn(BuildContext context) {
// return Container(
// width: 150, // Set a specific width for the table cell
// child: ThemedDateField(
// controller: controllers['enquiry_created_on'],
// hintText: "Select Date",
// borderColor: Colors.black,
// // Pass the date back to your item or state
// onDateSelected: (DateTime pickedDate) {
// setState(() {
// // Format it back to your backend requirement if needed
// String formatted = DateFormat('dd-MM-yyyy').format(pickedDate);
// controllers['enquiry_created_on']!.text = formatted;
// });
// },
// ),
// );
// }
// Widget buildEnquiryCreatedOn(BuildContext context) {
// return SizedBox(
// width: 140, // Adjust width to fit the column
// height: 35, // Standardized height for your inline edit row
// child: ThemedDateField(
// controller: controllers['enquiry_created_on'],
// hintText: "Select Date",
// borderColor: Colors.black,
// onDateSelected: (DateTime pickedDate) {
// setState(() {
// // Store only the date part in the controller
// String formatted = DateFormat('dd-MM-yyyy').format(pickedDate);
// controllers['enquiry_created_on']!.text = formatted;
// });
// },
// ),
// );
// }
Widget buildEnquiryCreatedOn(BuildContext context) {
return SizedBox(
height: 35,
width: 140, // Give it a fixed width so it doesn't disappear in the table
child: ThemedDateField(
controller: controllers['enquiry_created_on'],
hintText: "Select Date",
borderColor: Colors.black,
onDateSelected: (DateTime pickedDate) {
setState(() {
String formatted = DateFormat('dd-MM-yyyy').format(pickedDate);
controllers['enquiry_created_on']!.text = formatted;
});
},
),
);
}
Widget buildVehicleNumber(BuildContext context) {
return ThemedFormInlineField(
controller: controllers['regNo']!,
borderColor: Colors.black,
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) {
return ThemedFormInlineField(
controller: controllers['name']!,
// errorText: fieldErrors['name'],
borderColor: Colors.black,
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: 35,
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
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null
? selectedItem['vehicle_type'].toString()
: "", // ✅ FIXED
style: GoogleFonts.poppins(fontSize: 11, color: Colors.black),
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(
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Search Vehicle Type...",
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['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 ThemedFormInlineField(
controller: controllers['email']!,
borderColor: Colors.black,
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.26,
);
}
Widget buildPhNumber(BuildContext context) {
return ThemedFormInlineField(
controller: controllers['mobile']!,
borderColor: Colors.black,
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 ThemedFormField(
// maxLength: 500,
enableBorderWidth: 0.1,
controller: controllers['remarks']!,
borderColor: Colors.black,
verticalPad: 8,
horizonalPad: 10,
isdense: true,
// keyboardType: TextInputType.multiline,
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.1,
);
}
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 = ThemedUploadField(
hintText: hintText ?? "Upload Document",
txtwidth: width,
padVertical: 6,
padHorizontal: 8,
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 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 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 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 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',
);
}
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");
}
}
//---------------------- Enquiry TAB Logins End -------------------------------- //
@override
Widget build(BuildContext context) {
return MainLayout(
title: "Enquiries",
body: SelectionArea(
child: Container(
// color: Colors.yellow.shade50,
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(3.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// Container(
// height: 30,
// width: MediaQuery.of(context).size.width,
// child: GestureDetector(
// onTap: () {
// context.go(AppRoutes.dashboard);
// },
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Tooltip(
// message: 'Back',
// child: IconButton(
// icon: const Icon(Icons.arrow_left_sharp, size: 25),
// onPressed: () async {
// context.go(AppRoutes.dashboard);
// final prefs = await SharedPreferences.getInstance();
// await prefs.remove('dashboardKeyProvider');
// await prefs.remove('dashboardStatusProvider');
// await prefs.remove('dashboardStaffIdProvider');
// },
// splashRadius: 18,
// padding: const EdgeInsets.all(4),
// hoverColor: Colors.black12,
// constraints: const BoxConstraints(),
// ),
// ),
// const SizedBox(width: 5), // spacing between icon and text
// Text(
// "Enquiries",
// style: TextStyle(
// fontSize: 18,
// fontWeight: FontWeight.w600,
// ),
// ),
// ],
// ),
// ),
// ),
// 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,
// 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;
});
},
),
),
],
),
), ),
);
}
Widget _buildContent(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// if (ResponsiveLayout.isMobile(context)) ...[
// Container(
// padding: EdgeInsets.all(8.0),
// color: Color(0xffD9EBE8),
// child: DateFilterRow(
// key: ValueKey(SelectedStatus ?? ''),
// onFilterStaff: (val) {
// print('Selected Filterd STAFF Id - $val');
// SelectedStaffId = val;
// },
// selectedStaffId: SelectedStaffId,
// role: roleId,
// id: userId,
// selectedStatusVal: SelectedStatus,
// startController: controllers['startDate']!,
// endController: controllers['endDate']!,
// formKey: _formKey,
// isMobile: ResponsiveLayout.isMobile(context),
// onStatusChanged: (val) {
// SelectedStatus = val; // update parent
// },
//
// onFilter: () {
// // call your filter logic
// filterDateRange();
// },
// onRefresh: () {
// // call your refresh logic
// refrshfilterDateRange();
// },
// ),
// ),
// ],
Container(
// height: 40,
// color: Colors.pink,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
// height: 30,
// width: MediaQuery.of(context).size.width,
child: GestureDetector(
onTap: () {
context.go(AppRoutes.dashboard);
},
child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.start,
children: [
Tooltip(
message: 'Back',
child: IconButton(
icon: const Icon(Icons.arrow_left_sharp, size: 25),
onPressed: () async {
context.go(AppRoutes.dashboard);
final prefs = await SharedPreferences.getInstance();
await prefs.remove('dashboardKeyProvider');
await prefs.remove('dashboardStatusProvider');
await prefs.remove('dashboardStaffIdProvider');
},
splashRadius: 18,
padding: const EdgeInsets.all(4),
hoverColor: Colors.black12,
constraints: const BoxConstraints(),
),
),
const SizedBox(width: 5), // spacing between icon and text
Text(
"Enquiries",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
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,
),
tooltip: _showFilterRow ? 'Hide Filters' : 'Show Filters',
onPressed: () {
setState(() {
_showFilterRow = !_showFilterRow;
});
},
),
Spacer(),
ThemedSearchField(
hintText: 'Search',
backgroundColor: Color(0xFFF6F8F8),
onChanged: filterData,
txtHeight: 35,
controller: _searchStaffController,
txtwidth: ResponsiveLayout.isMobile(context)
? MediaQuery.of(context).size.width * 0.7
: MediaQuery.of(context).size.width * 0.13,
),
ResponsiveLayout.isMobile(context)
? Spacer()
: SizedBox(width: 10),
ExportBtn(
sheetName: "Enquiry",
fileName: "Enquiry_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context) ? true : false,
displayHeaders: [
// "Received Date & Time",
"Received Date",
// "Created Date & Time",
// "Enquiry Created Date",
"Partner",
"Assigned To",
"Insurer",
"Vehicle.No.",
"Insured Name",
"Assigned Date & Time",
"Premium",
"Payment Mode",
"Policy Number",
"Status",
],
keys: [
// "created_on",
"enquiry_created_on",
"agent_name",
"assigned_to_name",
"insurer_name",
"reg_no",
"insured_name",
"updated_on",
"premium_amount",
"payment_mode",
"policy_number",
"status",
],
),
SizedBox(width: 10),
GestureDetector(
onTap: () async {
// final prefs = await SharedPreferences.getInstance();
//
// await prefs.remove('enqAgentDataId');
// ref.read(enquiryIdProvider.notifier).state = null;
// context.go(AppRoutes.tabEnquiry);
// print('Export');
// Generate a unique ID for the new row
final newId = DateTime.now().millisecondsSinceEpoch;
// Clear all existing form controllers
for (String field in tabHeader) {
controllers[field]?.clear();
}
// Reset dropdown selections
selectedVehicleType = null;
selectedAgent = null;
selectedInsurer = null;
// Reset file uploads
docUploadedRCFile = null;
docUploadedIDProof = null;
docUploadedPrevPolicy = null;
selectedRCFile = null;
selectedIdProof = null;
selectedPrevPolicy = null;
setState(() {
// Create a new empty row data
final newRow = {
'id': newId.toString(), // Keep as String for consistency
'enquiry_created_on': '',
'agent_name': '',
'agent_id': '',
'assigned_to_name': '',
'insurer_name': '',
'insured_name': '',
'reg_no': '',
'vehicle_type': '',
'vehicle_type_id': '',
'email': '',
'mobile': '',
'remarks': '',
'updated_on': '',
'premium_amount': '',
'payment_mode': '',
'policy_number': '',
'status': '',
};
// Insert at the beginning of ALL lists
getStaffData.insert(0, newRow);
originalData.insert(0, newRow);
filteredData.insert(0, newRow);
// Set to editing mode immediately
isEditingRow[newId] = true;
// Store the ID as string
idPrimary = newId.toString();
selectedId = newId.toString();
// Reset to first page to see the new row
currentPage = 1;
});
},
child: Container(
padding: EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Color(0xFF425B5B),
borderRadius: BorderRadius.circular(8.0),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Tooltip(
message: 'Raise Enquiry',
child: Icon(Icons.add, color: Colors.white, size: 15),
),
// if (!ResponsiveLayout.isMobile(context)) ...[
// SizedBox(width: 10),
// Text(
// 'Raise Enquiry',
// style: GoogleFonts.inter(
// color: Colors.white,
// fontWeight: FontWeight.w600,
// fontSize: 14,
// ),
// ),
// ],
],
),
),
),
],
),
),
// SizedBox(height: 10),
// Container(
// decoration: BoxDecoration(
// color: const Color(0xFFEDF6F5),
// borderRadius: BorderRadius.circular(6),
// ),
// padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
// child: Row(
// children: const [
// Expanded(flex: 1, child: Text('S.No', style: _headerStyle)),
// Expanded(
// flex: 2,
// child: Text('Received Date', style: _headerStyle),
// ),
// Expanded(flex: 2, child: Text('Partner *', style: _headerStyle)),
// Expanded(
// flex: 2,
// child: Text('Assigned To *', style: _headerStyle),
// ),
// Expanded(flex: 2, child: Text('Broker *', style: _headerStyle)),
// Expanded(flex: 3, child: Text('Insurer *', style: _headerStyle)),
// Expanded(
// flex: 3,
// child: Text('Insured Name *', style: _headerStyle),
// ),
// Expanded(
// flex: 2,
// child: Text('Vehicle No *', style: _headerStyle),
// ),
// Expanded(
// flex: 2,
// child: Text('Vehicle Type *', style: _headerStyle),
// ),
// Expanded(flex: 2, child: Text('Email', style: _headerStyle)),
// Expanded(flex: 2, child: Text('Mobile', style: _headerStyle)),
// Expanded(flex: 2, child: Text('Documents', style: _headerStyle)),
// Expanded(flex: 2, child: Text('Remarks', style: _headerStyle)),
// Expanded(
// flex: 2,
// child: Text('Assigned Date', style: _headerStyle),
// ),
// Expanded(flex: 2, child: Text('Premium', style: _headerStyle)),
// Expanded(
// flex: 2,
// child: Text('Payment Mode', style: _headerStyle),
// ),
// Expanded(
// flex: 2,
// child: Text('Policy Number', style: _headerStyle),
// ),
// Expanded(flex: 2, child: Text('Status', style: _headerStyle)),
// Expanded(flex: 1, child: Text('Action', style: _headerStyle)),
// ],
// ),
// ),
ResponsiveLayout.isMobile(context)
? _buildDataTable(context)
: Expanded(child: _buildDataTable(context)),
],
);
}
List<Widget> _buildPopupDownload(BuildContext context, selectId, editing) {
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),
],
),
)
: Row(
children: [
buildDocRC(context, selectId),
SizedBox(width: 3),
buildDocIdProof(context, selectId),
SizedBox(width: 3),
buildDocPrevPolicy(context, selectId),
],
),
];
}
Widget _buildDataTable(BuildContext context) {
if (filteredData.isEmpty) {
return const SizedBox(
height: 50,
child: Center(child: Text('No available data')),
);
}
final sortedData = [..._paginatedData];
final startIndex = (currentPage - 1) * itemsPerPage;
final isDesktop = !ResponsiveLayout.isMobile(context);
// Build both-direction scrollable DataTable
return LayoutBuilder(
builder: (context, constraints) {
double minWidth = constraints.maxWidth < 1300
? 1300
: constraints.maxWidth;
return ScrollConfiguration(
behavior: const MaterialScrollBehavior().copyWith(
dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch},
),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: minWidth),
child: DataTable(
headingRowColor: WidgetStatePropertyAll(Color(0xFFEDF6F5)),
dividerThickness: 0.5,
headingRowHeight: 30,
columnSpacing: isDesktop ? 20.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: const [
// DataColumn(label: Text('S.No', style: _headerStyle)),
DataColumn(
label: Text('Received Date', style: _headerStyle),
),
DataColumn(label: Text('Partner *', style: _headerStyle)),
DataColumn(
label: Text('Assigned To *', style: _headerStyle),
),
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('Vehicle Type *', style: _headerStyle),
),
DataColumn(label: Text('Email', style: _headerStyle)),
DataColumn(label: Text('Mobile', style: _headerStyle)),
DataColumn(label: Text('Documents', style: _headerStyle)),
DataColumn(label: Text('Remarks', style: _headerStyle)),
DataColumn(label: Text('Action', style: _headerStyle)),
DataColumn(
label: Text('Assigned Date', style: _headerStyle),
),
DataColumn(label: Text('Premium', style: _headerStyle)),
DataColumn(
label: Text('Payment Mode', style: _headerStyle),
),
DataColumn(
label: Text('Policy Number', style: _headerStyle),
),
DataColumn(label: Text('Status', style: _headerStyle)),
],
rows: sortedData.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
final sno = startIndex + index + 1;
return _buildDataRow(item, sno);
}).toList(),
),
),
),
),
);
},
);
}
DataRow _buildDataRow(Map<String, dynamic> item, int sno) {
final id = int.tryParse(item['id'].toString()) ?? 0;
final selectId = item['id'].toString() ?? '0';
print('idPrimary - $idPrimary');
final editing = isEditingRow[id] ?? false;
final controllers = rowControllers[id];
final shouldHighlight = _shouldHighlightAssignedRow(item);
return DataRow(
color: shouldHighlight
? WidgetStatePropertyAll(
_isBlinkPhaseOn
? const Color(0xFFFFF3E0)
: const Color(0xFFFFFFFF),
)
: null,
cells: _decorateCellsForStaleAssigned([
// S.No.
// DataCell(Text('$sno', style: _dataBold)),
// Created On
// DataCell(
// Row(
// children: [
// // Text(item['created_on'], style: _dataBold),
// Text(
// formatDateTimeForTable(item['created_on'], newLine: true),
// style: _dataBold,
// ),
// ],
// ),
// ),
// Enquiry Created On
DataCell(
editing
? buildEnquiryCreatedOn(context)
: Text(item['enquiry_created_on'] ?? '-', style: _dataBold),
),
// Agent Name
DataCell(
editing
? buildAgentName(context)
: Text(item['agent_name'] ?? '-', style: _dataBold),
),
// Assigned To
DataCell(
editing
? buildSelectStaffMem(context)
: Text(item['assigned_to_name'] ?? '-', style: _dataBold),
),
DataCell(
editing
? buildBroker(context)
: Text(item['broker_name'] ?? '-', style: _dataBold),
),
// Insurer Name
DataCell(
editing
? buildInsurer(context)
: Text(item['insurer_short_name'] ?? '-', style: _dataBold),
),
DataCell(
editing
? buildInsuredName(context)
: Text(item['insured_name'] ?? '-', style: _dataBold),
),
// Vehicle No
DataCell(
editing
? buildVehicleNumber(context)
: Text(item['reg_no'] ?? '-', style: _dataBold),
),
DataCell(
editing
? buildVehicleType(context)
: Text(
_wrapText(item['vehicle_type'] ?? '-', 13),
style: _dataBold,
softWrap: true,
),
),
DataCell(
editing
? buildEmail(context)
: Text(item['email'] ?? '-', style: _dataBold),
),
DataCell(
editing
? buildPhNumber(context)
: Text(item['mobile'] ?? '-', style: _dataBold),
),
DataCell(
Builder(
builder: (buttonContext) => Material(
color: Colors.white,
child: InkWell(
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,
editing,
),
),
),
],
color: Colors.white,
);
},
child: Tooltip(
message: editing
? 'Click To Upload Documents'
: 'Click To View Documents',
waitDuration: const Duration(milliseconds: 500),
showDuration: const Duration(seconds: 2),
child: Icon(
editing ? Icons.upload_file : Icons.download_for_offline,
color: editing ? Colors.blue : Colors.green,
size: 22,
),
),
),
),
),
),
DataCell(
editing
? buildRemarks(context)
: Tooltip(
message: item['remarks'] ?? '-', // full content on hover
waitDuration: const Duration(milliseconds: 400),
showDuration: const Duration(seconds: 2),
child: Text(
_truncateText(item['remarks'] ?? '-', 10),
style: _dataBold,
overflow: TextOverflow.ellipsis,
),
),
),
// Actions
DataCell(
editing
? Row(
children: [
IconButton(
icon: const Icon(Icons.check, color: Colors.green),
tooltip: "Save",
onPressed: () => handleSave(id),
),
IconButton(
icon: const Icon(Icons.close, color: Colors.red),
tooltip: "Cancel",
onPressed: () => handleCancel(id),
),
],
)
: Row(
children: [
// if (roleId == 'manager') ...[
// if (item['status'] == 'Awaiting Proposal')
// Tooltip(
// message: "Assign Staff",
// child: IconButton(
// icon: Icon(Icons.assignment_ind_outlined, size: 15),
// onPressed: () {
// handleStaff(
// context,
// item,
// item['id'],
// item['reg_no'],
// );
// },
// splashRadius: 5,
// hoverColor: Colors.black12,
// padding: const EdgeInsets.all(4),
// constraints: const BoxConstraints(),
// ),
// ),
// ],
IconButton(
icon: const Icon(Icons.edit, color: Colors.blue),
tooltip: "Edit",
onPressed: () => handleEdit(item),
),
// IconButton(
// icon: const Icon(Icons.delete, color: Colors.red),
// tooltip: "Delete",
// onPressed: () => setState(() {
// getStaffData.removeWhere((e) => e['id'] == item['id']);
// }),
// ),
],
),
),
// Assigned Date (formatted)
DataCell(
Text(
// item['updated_on'] ?? '-',
formatDateTimeForTable(item['updated_on'], newLine: true),
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
// Premium
DataCell(
// ((item['premium_amount'] != null) &&
// (item['premium_amount'] != '') &&
// ((item['status'] != 'Awaiting Proposal') ||
// (item['status'] == 'Proposal Created') ||
// (item['status'] == 'Proposal Rejected')))
(item['status'] == 'Proposal Created' ||
item['status'] == 'Proposal Rejected')
? 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,
),
),
)
: Text(
item['premium_amount']?.toString() ?? '-',
style: _dataBold,
),
),
// Payment Mode
DataCell(Text(item['payment_mode'] ?? '-', style: _dataBold)),
// Policy Number
DataCell(
Text(
item['policy_number'] ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
// Status
DataCell(Text(item['status'] ?? '-', style: _dataBold)),
], shouldHighlight),
);
}
List<DataCell> _decorateCellsForStaleAssigned(
List<DataCell> cells,
bool shouldHighlight,
) {
if (!shouldHighlight) return cells;
final borderColor = _isBlinkPhaseOn
? const Color(0xFFE65100)
: const Color(0x00000000);
return cells
.map(
(cell) => DataCell(
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 3),
decoration: BoxDecoration(
border: Border.all(color: borderColor, width: 1.3),
borderRadius: BorderRadius.circular(2),
),
child: cell.child,
),
placeholder: cell.placeholder,
showEditIcon: cell.showEditIcon,
onTap: cell.onTap,
onDoubleTap: cell.onDoubleTap,
onLongPress: cell.onLongPress,
onTapDown: cell.onTapDown,
onTapCancel: cell.onTapCancel,
),
)
.toList();
}
bool _shouldHighlightAssignedRow(Map<String, dynamic> item) {
final rawStatus = (item['enquiry_status'] ?? '').toString().trim();
if (rawStatus.isEmpty) return false;
final normalizedStatus = rawStatus.toLowerCase();
final isAssignedStatus =
normalizedStatus == 'assigned' ||
(normalizedStatus.contains('assigned') &&
!normalizedStatus.contains('to be assigned'));
if (!isAssignedStatus) return false;
final assignedAt = _parseAssignedDateTime(item);
if (assignedAt == null) return false;
return DateTime.now().difference(assignedAt) >= const Duration(minutes: 30);
}
DateTime? _parseAssignedDateTime(Map<String, dynamic> item) {
final rawDate = [
item['assigned_to_datetime'],
item['assigned_datetime'],
item['assigned_on'],
item['updated_on'],
].firstWhere(
(value) => value != null && value.toString().trim().isNotEmpty,
orElse: () => null,
);
if (rawDate == null) return null;
final value = rawDate.toString().trim();
try {
return DateTime.parse(value);
} catch (_) {}
final parsers = [
DateFormat('dd-MM-yyyy hh:mm a'),
DateFormat('dd-MM-yyyy HH:mm'),
DateFormat('yyyy-MM-dd HH:mm:ss'),
DateFormat('yyyy-MM-dd HH:mm'),
];
for (final parser in parsers) {
try {
return parser.parse(value);
} catch (_) {
// try next parser
}
}
return null;
}
static final _dataBold = TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.black,
// color: Color(0xFF000000),
);
final _tableDataTimeStyle = GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w400,
);
static final _dataBoldsmall = TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
);
static final _dataSub = TextStyle(
fontSize: 10,
fontWeight: FontWeight.w300,
color: Color(0xFF585757),
);
static const _headerStyle = TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
);
static const _cardheaderStyle = TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 12,
);
static final TextStyle _textStyle = TextStyle(
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 : '-';
}
}
}