3086 lines
102 KiB
Dart
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 : '-';
|
|
}
|
|
}
|
|
}
|