1196 lines
37 KiB
Dart
1196 lines
37 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io' as html;
|
|
import 'package:dropdown_search/dropdown_search.dart';
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
|
|
import 'package:nhance_partner/presentation/screens/Enquiry/enquiry/tabs.dart';
|
|
import '../../../../core/config/env.dart';
|
|
import '../../../../core/services/api_service.dart';
|
|
import '../../../../data/services/auth_service.dart';
|
|
import '../../../../data/utils/toastNotification.dart';
|
|
import '../../../../data/utils/validators.dart';
|
|
import '../../../layouts/responsive_layout.dart';
|
|
import '../../../providers/manager_provider.dart';
|
|
import '../../../themes/indicators/customizd_file_upload.dart';
|
|
import '../../../themes/indicators/input_field_decoration.dart';
|
|
import '../../../themes/indicators/text_field_theme.dart';
|
|
import '../../../themes/indicators/upload_doc_theme.dart'
|
|
hide ThemedUploadField;
|
|
|
|
class QuotationTab extends ConsumerStatefulWidget {
|
|
// final Map<String, dynamic>? data;
|
|
final List<Map<String, dynamic>>? data;
|
|
String? id;
|
|
final Future<void> Function()? onRefresh;
|
|
QuotationTab({super.key, this.data, this.id, this.onRefresh});
|
|
@override
|
|
ConsumerState<QuotationTab> createState() => QuotationTabState();
|
|
}
|
|
|
|
class QuotationTabState extends ConsumerState<QuotationTab> {
|
|
int currentPage = 0;
|
|
int itemsPerPage = 10;
|
|
|
|
List<Map<String, dynamic>> dataVal = [];
|
|
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
late ApiService apiService;
|
|
final _formKey = GlobalKey<FormState>();
|
|
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
|
|
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
|
|
|
|
final GlobalKey<TabEnquiryListState> tabKey =
|
|
GlobalKey<TabEnquiryListState>();
|
|
|
|
bool blockKey = false;
|
|
|
|
List<String> tabHeader = [
|
|
'regNum',
|
|
'insurer',
|
|
'idv',
|
|
'insuranceId',
|
|
'premAmount',
|
|
'planType',
|
|
'addit_doc',
|
|
'remarks',
|
|
|
|
// 'mobile',
|
|
// 'code',
|
|
// 'address',
|
|
// 'regNo',
|
|
];
|
|
late String isActive = "1";
|
|
// html.File? docUploadedFile;
|
|
|
|
PlatformFile? docUploadedFile;
|
|
String? selectedFileNames;
|
|
String? docUploadedFileFromApi;
|
|
String? selectedId;
|
|
bool isLoading = false;
|
|
bool isquotation = false;
|
|
bool showAction = false;
|
|
|
|
Map<String, TextEditingController> controllers = {};
|
|
String? _token;
|
|
dynamic userId;
|
|
dynamic managerId;
|
|
dynamic role;
|
|
|
|
List<Map<String, dynamic>> getVehicleTypeData = [];
|
|
List<Map<String, dynamic>> filteredVechicleData = [];
|
|
|
|
List<Map<String, dynamic>> getInsurersData = [];
|
|
List<Map<String, dynamic>> filteredInsurersData = [];
|
|
|
|
Map<String, dynamic> dataDetails() {
|
|
final data = {
|
|
"id": selectedId,
|
|
"enquiry_id": widget.id,
|
|
"insured_declared_value": controllers["idv"]?.text,
|
|
"premium_amount": controllers["premAmount"]?.text,
|
|
"insurance_plan_type_id": controllers["insuranceId"]?.text,
|
|
"updated_by": userId,
|
|
};
|
|
return data;
|
|
}
|
|
|
|
List<Map<String, dynamic>> getQuotationData = [];
|
|
List<Map<String, dynamic>> originalData = [];
|
|
List<Map<String, dynamic>> filteredData = [];
|
|
|
|
// bool isLoading = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
apiService = ApiService();
|
|
|
|
for (String field in tabHeader) {
|
|
controllers[field] = TextEditingController();
|
|
}
|
|
// updateData();
|
|
_initializeToken();
|
|
Future.microtask(() {
|
|
managerId = ref.watch(managerIdProvider);
|
|
userId = ref.watch(userIdProvider);
|
|
role = ref.watch(userRoleProvider);
|
|
|
|
getQuotationList();
|
|
});
|
|
}
|
|
|
|
Future<void> _initializeToken() async {
|
|
_token = await AuthService.getToken();
|
|
print("APISERTOKEN - $_token");
|
|
}
|
|
|
|
void refresh() {
|
|
getQuotationList();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// Dispose all TextEditingControllers
|
|
for (var controller in controllers.values) {
|
|
controller.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> getQuotationList() async {
|
|
print('getClaimList called MagId - ');
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
|
|
try {
|
|
final response;
|
|
|
|
if (widget.data != null && widget.id != null) {
|
|
final data = List<Map<String, dynamic>>.from(widget.data as Iterable);
|
|
// response = await apiService.findSingleQuotationData(widget.id);
|
|
// print('quoationListData - ${widget.data}');
|
|
setState(() {
|
|
getQuotationData = data;
|
|
// getQuotationData = List<Map<String, dynamic>>.from(response['data']);
|
|
originalData = getQuotationData;
|
|
filteredData = List.from(originalData);
|
|
print('quoationListData - $getQuotationData');
|
|
|
|
blockKey = getQuotationData.any(
|
|
(item) => item['status'] == 'Accepted',
|
|
);
|
|
print('blockKey- $blockKey');
|
|
|
|
// blockKey = getQuotationData.any(
|
|
// (item) => (item['status']?.toString().toLowerCase() ?? '') == 'approved',
|
|
// );
|
|
|
|
// print('originalData - $getClaimPolicies');
|
|
});
|
|
|
|
// if (response['status'] == 'success') {
|
|
// final rawData = response['data'];
|
|
// print('quoationListData1 - ${response['data']}');
|
|
// setState(() {
|
|
// if (rawData is List) {
|
|
// // already a list
|
|
// getQuotationData = List<Map<String, dynamic>>.from(rawData);
|
|
// } else if (rawData is Map) {
|
|
// // single object, wrap into a list
|
|
// getQuotationData = [Map<String, dynamic>.from(rawData)];
|
|
// } else {
|
|
// getQuotationData = [];
|
|
// }
|
|
// // getQuotationData = List<Map<String, dynamic>>.from(response['data']);
|
|
// originalData = getQuotationData;
|
|
// filteredData = List.from(originalData);
|
|
// // print('originalData - $getClaimPolicies');
|
|
// });
|
|
// } else {
|
|
// getQuotationData = [];
|
|
// originalData = [];
|
|
// }
|
|
}
|
|
} catch (e) {
|
|
print('Exception occurred: $e');
|
|
} finally {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
void findAndUpdateById(updateId) {
|
|
try {
|
|
final match = getQuotationData.firstWhere(
|
|
(q) => q['id'] == updateId,
|
|
orElse: () => {},
|
|
);
|
|
|
|
if (match.isNotEmpty) {
|
|
print("Found quotation: $match");
|
|
updateData(match);
|
|
} else {
|
|
print("No quotation found with id $updateId");
|
|
}
|
|
} catch (e) {
|
|
print("Error finding quotation: $e");
|
|
}
|
|
}
|
|
|
|
void updateData(Map<String, dynamic> data) {
|
|
print('UPDID - $data}');
|
|
|
|
setState(() {
|
|
controllers['regNum']?.text = data['reg_no']?.toString() ?? '';
|
|
controllers['insurer']?.text = data['insurer_name']?.toString() ?? '';
|
|
controllers['idv']?.text =
|
|
data['insured_declared_value']?.toString() ?? '';
|
|
controllers['premAmount']?.text =
|
|
data['premium_amount']?.toString() ?? '';
|
|
|
|
controllers['insuranceId']?.text =
|
|
data['insurance_plan_type_id']?.toString() ?? '';
|
|
controllers['planType']?.text =
|
|
data['insurance_plan_type']?.toString() ?? '';
|
|
|
|
controllers['remarks']?.text = data['reject_reason']?.toString() ?? '';
|
|
|
|
String? status = data['status']?.toString() ?? '';
|
|
if (status == 'Pending') {
|
|
print('status - $status');
|
|
showAction = true;
|
|
}
|
|
|
|
String? apiDocPath = data["additional_uploaded_file_name"];
|
|
if (apiDocPath != null && apiDocPath.isNotEmpty) {
|
|
print('apiDocPath - $apiDocPath');
|
|
selectedFileNames = apiDocPath.split('/').last;
|
|
print('selectedFileNames - $selectedFileNames');
|
|
docUploadedFileFromApi = apiDocPath;
|
|
print('docUploadedFileFromApi - $docUploadedFileFromApi');
|
|
docUploadedFile = null;
|
|
} else {
|
|
selectedFileNames = null;
|
|
docUploadedFile = null;
|
|
docUploadedFileFromApi = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
void handleActions(data) {
|
|
print('Action - $data');
|
|
createData(data);
|
|
}
|
|
|
|
Future<void> createData(val) async {
|
|
final String apiUrldata;
|
|
apiUrldata = '${Env.apiUrl}quotation/acceptOrRejectQuotation';
|
|
|
|
// final token = await getToken(); // Fetch token
|
|
|
|
if (_token == null) {
|
|
throw Exception('Token not found. Please log in.');
|
|
}
|
|
|
|
final Map<String, dynamic> data = {
|
|
"id": selectedId,
|
|
"status": val,
|
|
"action_by": userId,
|
|
// "action_user": (role == 'agent') ? 'agent' : 'handler',
|
|
"action_user": role,
|
|
};
|
|
|
|
// data['id'] = selectedId; // Add plan_id for update
|
|
// data['status'] = val;
|
|
|
|
print("data------- $data");
|
|
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse(apiUrldata),
|
|
headers: {
|
|
'Authorization': 'Bearer $_token',
|
|
'Content-Type': 'application/json',
|
|
'app-signature': Env.App_Signature,
|
|
},
|
|
body: jsonEncode(data), // Convert map to JSON
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
print("Response: ${response.body}");
|
|
|
|
ToastHelper.showSuccessToast(context, 'Status Updated');
|
|
print("tre1");
|
|
selectedId = null; // just update field
|
|
isquotation = false;
|
|
print("tre11");
|
|
|
|
// 🔄 Refresh parent
|
|
if (widget.onRefresh != null) {
|
|
await widget.onRefresh!();
|
|
}
|
|
|
|
// refresh();
|
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
// tabKey.currentState?.loadQuotationTab(widget.id ?? "");
|
|
// });
|
|
// context.go(AppRoutes.staffLst);
|
|
} else if (response.statusCode == 403) {
|
|
await apiService.clearLocalStorageAndRedirect();
|
|
} else {
|
|
final responseBody = jsonDecode(response.body);
|
|
dynamic msg = responseBody['data'];
|
|
ToastHelper.showErrorToast(context, 'Status Updation Failed');
|
|
print("Failed to submit plan. Status: ${response.statusCode}");
|
|
print("Error: ${response.body}");
|
|
}
|
|
} catch (e) {
|
|
print(" Error submitting Staff: $e");
|
|
}
|
|
}
|
|
|
|
void UpdataDocuments() {
|
|
final dataSet = dataDetails();
|
|
print('UpdataDocuments - $dataSet}');
|
|
createDocumentData(dataSet);
|
|
}
|
|
|
|
Future<void> createDocumentData(Map<String, dynamic> userData) async {
|
|
final id = widget.id;
|
|
final uri = Uri.parse('${Env.apiUrl}quotation/updateQuotation');
|
|
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;
|
|
|
|
print("USerDAta - $userData");
|
|
|
|
// userData.forEach((key, value) {
|
|
// request.fields[key] = value.toString();
|
|
// print("✅ Encoded travel_details2: ${request.fields[key]}");
|
|
// });
|
|
|
|
userData.forEach((key, value) {
|
|
if (key != 'additional_uploaded_file_name') {
|
|
request.fields[key] = value.toString();
|
|
print("✅ Encoded $key: ${request.fields[key]}");
|
|
}
|
|
});
|
|
|
|
// Attach file if selected
|
|
if (docUploadedFile != null) {
|
|
try {
|
|
if (docUploadedFile!.bytes != null) {
|
|
final multipartFile = http.MultipartFile.fromBytes(
|
|
'additional_uploaded_file_name',
|
|
docUploadedFile!.bytes!,
|
|
filename: docUploadedFile!.name,
|
|
);
|
|
request.files.add(multipartFile);
|
|
} else if (docUploadedFile!.path != null) {
|
|
final multipartFile = await http.MultipartFile.fromPath(
|
|
'additional_uploaded_file_name',
|
|
docUploadedFile!.path!,
|
|
filename: docUploadedFile!.name,
|
|
);
|
|
request.files.add(multipartFile);
|
|
}
|
|
print("📎 File attached: ${docUploadedFile!.name}");
|
|
} catch (e) {
|
|
print("❌ Failed to attach file: $e");
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
ToastHelper.showSuccessToast(context, 'Document Uploaded');
|
|
print("Response: ${response.body}");
|
|
|
|
if (widget.onRefresh != null) {
|
|
await widget.onRefresh!();
|
|
}
|
|
// context.go(AppRoutes.agentLst);
|
|
} else if (response.statusCode == 403) {
|
|
await apiService.clearLocalStorageAndRedirect();
|
|
} else {
|
|
final responseBody = jsonDecode(response.body);
|
|
dynamic msg = responseBody['data'];
|
|
print("❌ Submission failed. Status: ${response.statusCode}");
|
|
print("Body: ${response.body}");
|
|
ToastHelper.showErrorToast(context, 'Document Upload Failed');
|
|
}
|
|
} catch (e) {
|
|
print("🔥 Error submitting user: $e");
|
|
}
|
|
}
|
|
|
|
Color _getStatusColor(String? status) {
|
|
switch (status) {
|
|
case 'Pending':
|
|
return Colors.yellow;
|
|
case 'Accepted':
|
|
return Colors.green;
|
|
case 'Rejected':
|
|
return Colors.red;
|
|
default:
|
|
return Colors.grey;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
bool isMobile = ResponsiveLayout.isMobile(context);
|
|
return SelectionArea(
|
|
child: Container(
|
|
height: MediaQuery.of(context).size.height,
|
|
width: MediaQuery.of(context).size.width,
|
|
child: filteredData.isNotEmpty
|
|
? SingleChildScrollView(
|
|
scrollDirection: Axis.vertical,
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
// color: Colors.green,
|
|
decoration: BoxDecoration(
|
|
color: Color(0xffEDF6F5),
|
|
borderRadius: BorderRadius.circular(10.0),
|
|
),
|
|
width: MediaQuery.of(context).size.width,
|
|
// height: MediaQuery.of(context).size.height * 0.8,
|
|
padding: isMobile
|
|
? EdgeInsets.all(6.0)
|
|
: EdgeInsets.all(16.0),
|
|
child: Column(
|
|
children: [
|
|
// Text('datta')
|
|
_buildDataTable(context),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
if (isquotation)
|
|
Container(
|
|
// color: Colors.green,
|
|
decoration: BoxDecoration(
|
|
color: Color(0xffEDF6F5),
|
|
borderRadius: BorderRadius.circular(10.0),
|
|
),
|
|
width: MediaQuery.of(context).size.width,
|
|
// height: MediaQuery.of(context).size.height * 0.8,
|
|
padding: isMobile
|
|
? EdgeInsets.only(left: 14.0, right: 14.0)
|
|
: EdgeInsets.all(26.0),
|
|
child: Column(
|
|
children: [
|
|
SingleChildScrollView(
|
|
child: buildFormFields(context),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
if (!blockKey)
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
GestureDetector(
|
|
onTap: () {
|
|
handleActions('Rejected');
|
|
},
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 45.0,
|
|
vertical: 8,
|
|
),
|
|
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
color: Color(0xFF425B5B),
|
|
),
|
|
child: Text(
|
|
'Reject',
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
GestureDetector(
|
|
onTap: () {
|
|
handleActions('Accepted');
|
|
},
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 45.0,
|
|
vertical: 8,
|
|
),
|
|
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
color: Color(0xFF425B5B),
|
|
),
|
|
child: Text(
|
|
'Accept',
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: Center(child: Text('No Available Data')),
|
|
) );
|
|
}
|
|
|
|
Widget _buildDataTable(BuildContext context) {
|
|
if (filteredData.isEmpty) {
|
|
return const SizedBox(
|
|
height: 50,
|
|
child: Center(child: Text('No available data')),
|
|
);
|
|
}
|
|
|
|
final sortedData = [...filteredData]
|
|
..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
|
|
|
|
return SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
ListView.builder(
|
|
shrinkWrap: true,
|
|
physics: NeverScrollableScrollPhysics(),
|
|
itemCount: ResponsiveLayout.isMobile(context)
|
|
? sortedData
|
|
.length // only cards for mobile
|
|
: sortedData.length + 1, // +1 for header in desktop
|
|
itemBuilder: (context, index) {
|
|
if (!ResponsiveLayout.isMobile(context) && index == 0) {
|
|
return _buildHeader();
|
|
}
|
|
|
|
final startIndex = (currentPage - 1) * itemsPerPage;
|
|
final item =
|
|
sortedData[index -
|
|
(ResponsiveLayout.isMobile(context) ? 0 : 1)];
|
|
final sno = startIndex + index;
|
|
|
|
return !ResponsiveLayout.isMobile(context)
|
|
? _buildDataRow(item, sno)
|
|
: _buildDataCard(item, sno);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildFormFields(BuildContext context) {
|
|
return Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildResponsiveRow(
|
|
context,
|
|
buildName(context),
|
|
buildInsusrer(context),
|
|
),
|
|
if (!ResponsiveLayout.isMobile(context)) const SizedBox(height: 10),
|
|
|
|
_buildResponsiveRow(
|
|
context,
|
|
buildIdv(context),
|
|
buildPremiumAmnt(context),
|
|
),
|
|
if (!ResponsiveLayout.isMobile(context)) const SizedBox(height: 10),
|
|
|
|
_buildResponsiveRow(
|
|
context,
|
|
buildPlanType(context),
|
|
SizedBox.shrink(),
|
|
// buildDocuments(context),
|
|
),
|
|
|
|
// if (!ResponsiveLayout.isMobile(context)) const SizedBox(height: 10),
|
|
// _buildResponsiveRow(
|
|
// context,
|
|
// buildAdditonalDocuments(context),
|
|
// buildRemarks(context),
|
|
// ),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildResponsiveRow(
|
|
BuildContext context,
|
|
Widget first,
|
|
Widget second,
|
|
) {
|
|
if (ResponsiveLayout.isMobile(context)) {
|
|
// Stack vertically
|
|
return Column(children: [first, const SizedBox(height: 2), second]);
|
|
} else {
|
|
// Place side by side
|
|
return Row(
|
|
children: [
|
|
Expanded(child: first),
|
|
const SizedBox(width: 25),
|
|
Expanded(child: second),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
Widget buildName(BuildContext context) {
|
|
return buildResponsiveField(
|
|
label: "Vehicle Number",
|
|
field: ThemedFormField(
|
|
controller: controllers['regNum']!,
|
|
readOnly: true,
|
|
// validator: (value) => Validators.requiredField(value, "regNum"),
|
|
txtwidth: ResponsiveLayout.isMobile(context)
|
|
? null
|
|
: MediaQuery.of(context).size.width * 0.26,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildInsusrer(BuildContext context) {
|
|
return buildResponsiveField(
|
|
label: "Insurer",
|
|
field: ThemedFormField(
|
|
controller: controllers['insurer']!,
|
|
readOnly: true,
|
|
txtwidth: ResponsiveLayout.isMobile(context)
|
|
? null
|
|
: MediaQuery.of(context).size.width * 0.26,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildIdv(BuildContext context) {
|
|
return buildResponsiveField(
|
|
label: "IDV",
|
|
field: ThemedFormField(
|
|
controller: controllers['idv']!,
|
|
readOnly: true,
|
|
|
|
txtwidth: ResponsiveLayout.isMobile(context)
|
|
? null
|
|
: MediaQuery.of(context).size.width * 0.26,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildPremiumAmnt(BuildContext context) {
|
|
return buildResponsiveField(
|
|
label: "Premium Amount",
|
|
field: ThemedFormField(
|
|
controller: controllers['premAmount']!,
|
|
readOnly: true,
|
|
txtwidth: ResponsiveLayout.isMobile(context)
|
|
? null
|
|
: MediaQuery.of(context).size.width * 0.26,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildPlanType(BuildContext context) {
|
|
return buildResponsiveField(
|
|
label: "Plan Type",
|
|
field: ThemedFormField(
|
|
controller: controllers['planType']!,
|
|
readOnly: true,
|
|
txtwidth: ResponsiveLayout.isMobile(context)
|
|
? null
|
|
: MediaQuery.of(context).size.width * 0.26,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildDocuments(BuildContext context) {
|
|
return buildResponsiveUploadField(
|
|
label: 'Proposal Documents',
|
|
hintText: selectedFileNames,
|
|
onFileSelected: (fileName, file) {
|
|
setState(() {
|
|
docUploadedFile = file;
|
|
selectedFileNames = fileName;
|
|
|
|
UpdataDocuments();
|
|
});
|
|
},
|
|
showDownload: docUploadedFile != null || docUploadedFileFromApi != null,
|
|
onRemove: () {
|
|
setState(() {
|
|
docUploadedFile = null;
|
|
docUploadedFileFromApi = null;
|
|
selectedFileNames = null;
|
|
});
|
|
},
|
|
onDownload: () => apiService.downloadFile(
|
|
apiUrl: 'quotation/downloadAdditionalUploadedFile?id=$selectedId',
|
|
apiId: selectedId,
|
|
localFile: docUploadedFile,
|
|
fileName: selectedFileNames,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Widget buildAdditonalDocuments(BuildContext context) {
|
|
// return buildResponsiveField(
|
|
// label: "Additional Documents",
|
|
// field: ThemedFormField(
|
|
// controller: controllers['addit_doc']!,
|
|
// // validator: (value) => Validators.phone(value, "phNumber"),
|
|
// txtwidth: ResponsiveLayout.isMobile(context)
|
|
// ? null
|
|
// : MediaQuery.of(context).size.width * 0.26,
|
|
// ),
|
|
// );
|
|
// }
|
|
//
|
|
// Widget buildRemarks(BuildContext context) {
|
|
// return buildResponsiveField(
|
|
// label: "Remarks",
|
|
// field: ThemedFormField(
|
|
// controller: controllers['remarks']!,
|
|
// // validator: (value) => Validators.phone(value, "phNumber"),
|
|
// txtwidth: ResponsiveLayout.isMobile(context)
|
|
// ? null
|
|
// : MediaQuery.of(context).size.width * 0.26,
|
|
// ),
|
|
// );
|
|
// }
|
|
|
|
Widget buildResponsiveField({required String label, required Widget field}) {
|
|
final isMobile = ResponsiveLayout.isMobile(context);
|
|
|
|
if (isMobile) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label, style: _textStyle),
|
|
const SizedBox(height: 8),
|
|
field,
|
|
const SizedBox(height: 16),
|
|
],
|
|
);
|
|
} else {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(child: Text(label, style: _textStyle)),
|
|
const SizedBox(width: 10),
|
|
field,
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// Widget buildResponsiveUploadField({
|
|
// required String label,
|
|
// required String? hintText,
|
|
// required void Function(String fileName, dynamic file) onFileSelected,
|
|
// })
|
|
// {
|
|
// final isMobile = ResponsiveLayout.isMobile(context);
|
|
//
|
|
// final uploadWidget = ThemedUploadField(
|
|
// hintText: hintText ?? "Upload Document",
|
|
// txtwidth: isMobile ? null : MediaQuery.of(context).size.width * 0.26,
|
|
// onFileSelected: onFileSelected,
|
|
// );
|
|
//
|
|
// if (isMobile) {
|
|
// return Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// Text(label, style: _textStyle),
|
|
// const SizedBox(height: 8),
|
|
// uploadWidget,
|
|
// const SizedBox(height: 16),
|
|
// ],
|
|
// );
|
|
// } else {
|
|
// return Row(
|
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
// children: [
|
|
// Expanded(child: Text(label, style: _textStyle)),
|
|
// const SizedBox(width: 10),
|
|
// uploadWidget,
|
|
// ],
|
|
// );
|
|
// }
|
|
// }
|
|
|
|
Widget buildResponsiveUploadField({
|
|
required String label,
|
|
required String? hintText,
|
|
required void Function(String fileName, dynamic file) onFileSelected,
|
|
bool showDownload = false,
|
|
VoidCallback? onRemove,
|
|
VoidCallback? onDownload,
|
|
}) {
|
|
final isMobile = ResponsiveLayout.isMobile(context);
|
|
|
|
final uploadWidget = ThemedUploadField(
|
|
hintText: hintText ?? "Upload Document",
|
|
txtwidth: isMobile ? null : MediaQuery.of(context).size.width * 0.26,
|
|
onFileSelected: onFileSelected,
|
|
);
|
|
|
|
final downloadRow = showDownload
|
|
? Padding(
|
|
padding: const EdgeInsets.only(top: 5),
|
|
child: Row(
|
|
children: [
|
|
// IconButton(
|
|
// onPressed: onRemove,
|
|
// icon: const Icon(
|
|
// Icons.remove_circle_outline_rounded,
|
|
// color: Colors.redAccent,
|
|
// ),
|
|
// tooltip: 'Remove Upload',
|
|
// ),
|
|
GestureDetector(
|
|
onTap: onDownload,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(5),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(5),
|
|
color: const Color(0xFF425B5B),
|
|
),
|
|
child: Row(
|
|
children: const [
|
|
Text(
|
|
"Download",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
SizedBox(width: 5),
|
|
Icon(Icons.download, size: 13, color: Colors.white),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: const SizedBox.shrink();
|
|
|
|
if (isMobile) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label, style: _textStyle),
|
|
const SizedBox(height: 8),
|
|
uploadWidget,
|
|
downloadRow,
|
|
const SizedBox(height: 16),
|
|
],
|
|
);
|
|
} else {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(child: Text(label, style: _textStyle)),
|
|
const SizedBox(width: 10),
|
|
uploadWidget,
|
|
],
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [SizedBox.shrink(), downloadRow],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
Widget buildId(BuildContext context) {
|
|
return buildResponsiveField(
|
|
label: "Vehicle Number *",
|
|
field: ThemedFormField(
|
|
controller: controllers['regNo']!,
|
|
validator: (value) => Validators.requiredField(value, "regNo"),
|
|
txtwidth: ResponsiveLayout.isMobile(context)
|
|
? null
|
|
: MediaQuery.of(context).size.width * 0.26,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHeader() {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: Color(0xFFEDF6F5),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
|
child: const Row(
|
|
children: [
|
|
Expanded(flex: 1, child: Text(' ', style: _headerStyle)),
|
|
Expanded(flex: 1, child: Text('Vehicle Number', style: _headerStyle)),
|
|
Expanded(flex: 1, child: Text('Insurer', style: _headerStyle)),
|
|
Expanded(flex: 1, child: Text('IDV', style: _headerStyle)),
|
|
Expanded(flex: 1, child: Text('Plan Type', style: _headerStyle)),
|
|
Expanded(flex: 1, child: Text('Premium Amount', style: _headerStyle)),
|
|
Expanded(flex: 1, child: Text('Status', style: _headerStyle)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDataRow(Map<String, dynamic> item, sno) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16),
|
|
// margin: const EdgeInsets.only(top: 10),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
// color: Color(0xFFE0F7F9),
|
|
border: const Border(
|
|
bottom: BorderSide(color: Color(0xFFEAEAEA), width: 1),
|
|
),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
// ✅ Single-select checkbox
|
|
Expanded(
|
|
flex: 1,
|
|
child: Checkbox(
|
|
activeColor: Colors.black,
|
|
value: selectedId == item['id'], // only selected row is checked
|
|
onChanged: (val) {
|
|
setState(() {
|
|
if (val == true) {
|
|
selectedId = item['id']; // select this row
|
|
isquotation = true;
|
|
} else {
|
|
selectedId = null;
|
|
isquotation = false; // unselect
|
|
}
|
|
});
|
|
|
|
findAndUpdateById(selectedId);
|
|
print("Selected row id: $selectedId");
|
|
},
|
|
),
|
|
),
|
|
|
|
Expanded(
|
|
flex: 1,
|
|
child: Text(item['reg_no'] ?? '-', style: _dataBold),
|
|
),
|
|
Expanded(
|
|
flex: 1,
|
|
child: Text(item['insurer_name'] ?? '-', style: _dataBold),
|
|
),
|
|
Expanded(
|
|
flex: 1,
|
|
child: Text(
|
|
item['insured_declared_value'] ?? '-',
|
|
style: _dataBold,
|
|
),
|
|
),
|
|
Expanded(
|
|
flex: 1,
|
|
child: Text(item['insurance_plan_type'] ?? '-', style: _dataBold),
|
|
),
|
|
Expanded(
|
|
flex: 1,
|
|
child: Text(item['premium_amount'] ?? '-', style: _dataBold),
|
|
),
|
|
Expanded(
|
|
flex: 1,
|
|
child: Text(
|
|
item['status'] ?? '-',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
color: _getStatusColor(item['status']),
|
|
// color: item['status'] == 'Accepted' ? Colors.green : Colors.red,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDataCard(Map<String, dynamic> item, int sno) {
|
|
return Container(
|
|
// margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFFFFFF),
|
|
// color: const Color(0xFFF6FEFD),
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
border: Border.all(color: const Color(0xffD9EBE8)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Column(
|
|
children: [
|
|
// ✅ Single-select checkbox
|
|
Checkbox(
|
|
activeColor: Colors.black,
|
|
value: selectedId == item['id'], // only selected row is checked
|
|
onChanged: (val) {
|
|
setState(() {
|
|
if (val == true) {
|
|
selectedId = item['id']; // select this row
|
|
isquotation = true;
|
|
} else {
|
|
selectedId = null;
|
|
isquotation = false; // unselect
|
|
}
|
|
});
|
|
|
|
findAndUpdateById(selectedId);
|
|
print("Selected row id: $selectedId");
|
|
},
|
|
),
|
|
],
|
|
),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
// flex: 2,
|
|
child: Text(
|
|
item['insurer_name'] ?? '-',
|
|
style: _cardRow1BodyStyle,
|
|
),
|
|
),
|
|
|
|
Expanded(
|
|
// flex: 2,
|
|
child: Text(
|
|
item['insurance_plan_type'] ?? '-',
|
|
style: _cardRow1BodyStyle,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
// "TN64V3456",
|
|
item['reg_no'] ?? '-',
|
|
style: _cardheaderStyle,
|
|
),
|
|
Text(
|
|
'IDV: ${item['insured_declared_value']}' ?? '-',
|
|
style: _cardRow2BodyStyle,
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
RichText(
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: 'Status : ', // key
|
|
style: TextStyle(
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 11,
|
|
), // key color
|
|
),
|
|
TextSpan(
|
|
text: '${item['status'] ?? '-'}', // value
|
|
style: TextStyle(
|
|
color: _getStatusColor(item['status']),
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 11,
|
|
), // value color
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
Text(
|
|
'${item['premium_amount']}' ?? '-',
|
|
style: _cardheaderStyle,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
static final _dataBold = TextStyle(
|
|
fontSize: 14,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
color: Color(0xFF000000),
|
|
);
|
|
|
|
static final _dataSub = TextStyle(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w300,
|
|
color: Color(0xFF585757),
|
|
);
|
|
|
|
static const _headerStyle = TextStyle(
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.bold,
|
|
);
|
|
|
|
static const _textStyle = TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
);
|
|
|
|
static const _cardheaderStyle = TextStyle(
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12,
|
|
);
|
|
|
|
static const _cardBodyStyle = TextStyle(
|
|
// color: Color(0xFF545454),
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 12,
|
|
);
|
|
static const _cardRow1BodyStyle = TextStyle(
|
|
// color: Color(0xFF545454),
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 12,
|
|
);
|
|
static const _cardRow2BodyStyle = TextStyle(
|
|
color: Color(0xFF545454),
|
|
// color: Colors.black,
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 12,
|
|
);
|
|
}
|