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