1356 lines
60 KiB
Dart
1356 lines
60 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:pdf_render/pdf_render_widgets.dart';
|
|
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
|
|
|
import '../../../../core/config/env.dart';
|
|
import '../../../../core/services/api_service.dart';
|
|
import '../../../../data/utils/toastNotification.dart';
|
|
import '../../../providers/manager_provider.dart';
|
|
|
|
import 'loadNetworkPdfBytes.dart';
|
|
import 'dart:typed_data';
|
|
|
|
// /// Represents Homepage for Navigation
|
|
// class policyValidation extends ConsumerStatefulWidget {
|
|
// final dynamic item;
|
|
// const policyValidation({Key? key, this.item}) : super(key: key);
|
|
//
|
|
// @override
|
|
// ConsumerState<policyValidation> createState() => _policyValidationState();
|
|
// }
|
|
//
|
|
// class _policyValidationState extends ConsumerState<policyValidation> {
|
|
// final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();
|
|
//
|
|
// @override
|
|
// void initState() {
|
|
// super.initState();
|
|
// }
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// return Scaffold(
|
|
// appBar: AppBar(
|
|
// title: const Text('Syncfusion Flutter PDF Viewer'),
|
|
// actions: <Widget>[
|
|
// IconButton(
|
|
// icon: const Icon(
|
|
// Icons.bookmark,
|
|
// color: Colors.white,
|
|
// semanticLabel: 'Bookmark',
|
|
// ),
|
|
// onPressed: () {
|
|
// _pdfViewerKey.currentState?.openBookmarkView();
|
|
// },
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// body: SfPdfViewer.asset(
|
|
// 'assets/pdfs/sample.pdf',
|
|
// key: _pdfViewerKey,
|
|
// )
|
|
// );
|
|
// }
|
|
// }
|
|
import 'package:flutter/services.dart';
|
|
|
|
final digitsOnlyFormatter = [FilteringTextInputFormatter.digitsOnly];
|
|
|
|
final decimalFormatter = [
|
|
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')),
|
|
];
|
|
|
|
class policyValidation extends ConsumerStatefulWidget {
|
|
final dynamic item;
|
|
const policyValidation({Key? key, this.item}) : super(key: key);
|
|
|
|
@override
|
|
ConsumerState<policyValidation> createState() => _policyValidationState();
|
|
}
|
|
|
|
class _policyValidationState extends ConsumerState<policyValidation> {
|
|
// --- Api / state
|
|
late ApiService apiService;
|
|
bool isLoading = false;
|
|
dynamic managerId;
|
|
dynamic userId;
|
|
|
|
String? pdfUrl; // full file path from API
|
|
|
|
// --- Controllers (all editable fields)
|
|
late TextEditingController policyNumberController;
|
|
late TextEditingController insuredNameController;
|
|
late TextEditingController premiumController;
|
|
late TextEditingController remarksController;
|
|
|
|
late TextEditingController rcNoController;
|
|
late TextEditingController issuedDateController;
|
|
late TextEditingController startDateController;
|
|
late TextEditingController endDateController;
|
|
|
|
late TextEditingController tpController;
|
|
late TextEditingController odController;
|
|
late TextEditingController paController;
|
|
late TextEditingController cgstController;
|
|
late TextEditingController sgstController;
|
|
late TextEditingController igstController;
|
|
|
|
late TextEditingController rtoStateCodeController;
|
|
late TextEditingController rtoCityCodeController;
|
|
late TextEditingController weightController;
|
|
late TextEditingController fuelTypeController;
|
|
late TextEditingController registrationDateController;
|
|
late TextEditingController yomController;
|
|
|
|
late TextEditingController engineNoController;
|
|
late TextEditingController chassisNoController;
|
|
late TextEditingController makeController;
|
|
late TextEditingController modelController;
|
|
late TextEditingController cubicCapacityController;
|
|
late TextEditingController vehicleTypeController;
|
|
|
|
late TextEditingController brokerNameController;
|
|
late TextEditingController commissionAmountController;
|
|
|
|
final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();
|
|
final _formKey = GlobalKey<FormState>();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
apiService = ApiService();
|
|
|
|
// instantiate controllers with defaults
|
|
policyNumberController = TextEditingController();
|
|
insuredNameController = TextEditingController();
|
|
premiumController = TextEditingController();
|
|
remarksController = TextEditingController();
|
|
|
|
rcNoController = TextEditingController();
|
|
issuedDateController = TextEditingController();
|
|
startDateController = TextEditingController();
|
|
endDateController = TextEditingController();
|
|
|
|
tpController = TextEditingController();
|
|
odController = TextEditingController();
|
|
paController = TextEditingController();
|
|
cgstController = TextEditingController();
|
|
sgstController = TextEditingController();
|
|
igstController = TextEditingController();
|
|
|
|
rtoStateCodeController = TextEditingController();
|
|
rtoCityCodeController = TextEditingController();
|
|
weightController = TextEditingController();
|
|
fuelTypeController = TextEditingController();
|
|
registrationDateController = TextEditingController();
|
|
yomController = TextEditingController();
|
|
|
|
engineNoController = TextEditingController();
|
|
chassisNoController = TextEditingController();
|
|
makeController = TextEditingController();
|
|
modelController = TextEditingController();
|
|
cubicCapacityController = TextEditingController();
|
|
vehicleTypeController = TextEditingController();
|
|
|
|
brokerNameController = TextEditingController();
|
|
commissionAmountController = TextEditingController();
|
|
|
|
// Pre-fill controllers from initial `widget.item` (if available)
|
|
final item = widget.item as Map<String, dynamic>? ?? {};
|
|
policyNumberController.text = item['policy_number']?.toString() ?? '';
|
|
insuredNameController.text = item['insured_name']?.toString() ?? '';
|
|
premiumController.text = item['premium_amount']?.toString() ?? '';
|
|
remarksController.text = item['remarks']?.toString() ?? '';
|
|
|
|
rcNoController.text = item['rc_no']?.toString() ?? '';
|
|
issuedDateController.text = item['issued_date']?.toString() ?? '';
|
|
startDateController.text = item['start_date']?.toString() ?? '';
|
|
endDateController.text = item['end_date']?.toString() ?? '';
|
|
|
|
tpController.text = item['tp']?.toString() ?? '';
|
|
odController.text = item['od']?.toString() ?? '';
|
|
paController.text = item['pa']?.toString() ?? '';
|
|
cgstController.text = item['cgst']?.toString() ?? '';
|
|
sgstController.text = item['sgst']?.toString() ?? '';
|
|
igstController.text = item['igst']?.toString() ?? '';
|
|
|
|
rtoStateCodeController.text = item['rto_state_code']?.toString() ?? '';
|
|
rtoCityCodeController.text = item['rto_city_code']?.toString() ?? '';
|
|
weightController.text = item['weight']?.toString() ?? '';
|
|
fuelTypeController.text = item['fuel_type']?.toString() ?? '';
|
|
registrationDateController.text =
|
|
item['date_of_registration']?.toString() ?? '';
|
|
yomController.text = item['year_of_manufacture']?.toString() ?? '';
|
|
|
|
engineNoController.text = item['engine_no']?.toString() ?? '';
|
|
chassisNoController.text = item['chassis_no']?.toString() ?? '';
|
|
makeController.text = item['make']?.toString() ?? '';
|
|
modelController.text = item['model']?.toString() ?? '';
|
|
cubicCapacityController.text = item['cubic_capacity']?.toString() ?? '';
|
|
vehicleTypeController.text = item['vehicle_type']?.toString() ?? '';
|
|
|
|
brokerNameController.text = item['broker_name']?.toString() ?? '';
|
|
commissionAmountController.text =
|
|
item['commission_amount']?.toString() ?? '';
|
|
|
|
// read providers + call APIs
|
|
Future.microtask(() {
|
|
try {
|
|
managerId = ref.read(managerIdProvider);
|
|
} catch (e) {
|
|
managerId = null;
|
|
}
|
|
try {
|
|
userId = ref.read(userIdProvider);
|
|
} catch (e) {
|
|
userId = null;
|
|
}
|
|
|
|
getFindPolicy();
|
|
getPolicyFilePath();
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
policyNumberController.dispose();
|
|
insuredNameController.dispose();
|
|
premiumController.dispose();
|
|
remarksController.dispose();
|
|
|
|
rcNoController.dispose();
|
|
issuedDateController.dispose();
|
|
startDateController.dispose();
|
|
endDateController.dispose();
|
|
|
|
tpController.dispose();
|
|
odController.dispose();
|
|
paController.dispose();
|
|
cgstController.dispose();
|
|
sgstController.dispose();
|
|
igstController.dispose();
|
|
|
|
rtoStateCodeController.dispose();
|
|
rtoCityCodeController.dispose();
|
|
weightController.dispose();
|
|
fuelTypeController.dispose();
|
|
registrationDateController.dispose();
|
|
yomController.dispose();
|
|
|
|
engineNoController.dispose();
|
|
chassisNoController.dispose();
|
|
makeController.dispose();
|
|
modelController.dispose();
|
|
cubicCapacityController.dispose();
|
|
vehicleTypeController.dispose();
|
|
|
|
brokerNameController.dispose();
|
|
commissionAmountController.dispose();
|
|
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> getPolicyFilePath() async {
|
|
_pdfViewerKey.currentState?.openBookmarkView();
|
|
|
|
final policyId = widget.item?['policy_id'];
|
|
if (policyId == null) {
|
|
debugPrint('policyValidation: policy id is null, skipping API call');
|
|
return;
|
|
}
|
|
|
|
if (!mounted) return;
|
|
setState(() => isLoading = true);
|
|
|
|
try {
|
|
final url = Uri.parse(
|
|
'${Env.apiUrl}policy/PolicyFilePath?policy_id=$policyId&file_type=policy_pdf',
|
|
);
|
|
|
|
setState(() {
|
|
pdfUrl = "$url";
|
|
});
|
|
print("FINAL PDF URL -> $pdfUrl");
|
|
} catch (e, st) {
|
|
print('Exception in getPolicyFilePath: $e\n$st');
|
|
} finally {
|
|
if (!mounted) return;
|
|
setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> getFindPolicy() async {
|
|
final policyId = widget.item?['policy_id'];
|
|
if (policyId == null) {
|
|
debugPrint('policyValidation: policy id is null, skipping API call');
|
|
return;
|
|
}
|
|
|
|
if (!mounted) return;
|
|
setState(() => isLoading = true);
|
|
|
|
try {
|
|
final response = await apiService.findPolicyApi(policyId);
|
|
debugPrint('findPolicyApi response: $response');
|
|
|
|
if (response != null && response['status'] == 'success') {
|
|
final data = (response['data'] ?? {}) as Map<String, dynamic>;
|
|
|
|
if (!mounted) return;
|
|
setState(() {
|
|
// update controllers (overwrite with authoritative API values)
|
|
policyNumberController.text =
|
|
data['policy_number']?.toString() ?? policyNumberController.text;
|
|
insuredNameController.text =
|
|
data['insured_name']?.toString() ?? insuredNameController.text;
|
|
premiumController.text =
|
|
data['premium_amount']?.toString() ?? premiumController.text;
|
|
remarksController.text =
|
|
data['remarks']?.toString() ?? remarksController.text;
|
|
|
|
rcNoController.text =
|
|
data['rc_no']?.toString() ?? rcNoController.text;
|
|
issuedDateController.text =
|
|
data['issued_date']?.toString() ?? issuedDateController.text;
|
|
startDateController.text =
|
|
data['start_date']?.toString() ?? startDateController.text;
|
|
endDateController.text =
|
|
data['end_date']?.toString() ?? endDateController.text;
|
|
|
|
tpController.text = data['tp']?.toString() ?? tpController.text;
|
|
odController.text = data['od']?.toString() ?? odController.text;
|
|
paController.text = data['pa']?.toString() ?? paController.text;
|
|
cgstController.text = data['cgst']?.toString() ?? cgstController.text;
|
|
sgstController.text = data['sgst']?.toString() ?? sgstController.text;
|
|
igstController.text = data['igst']?.toString() ?? igstController.text;
|
|
|
|
rtoStateCodeController.text =
|
|
data['rto_state_code']?.toString() ?? rtoStateCodeController.text;
|
|
rtoCityCodeController.text =
|
|
data['rto_city_code']?.toString() ?? rtoCityCodeController.text;
|
|
weightController.text =
|
|
data['weight']?.toString() ?? weightController.text;
|
|
fuelTypeController.text =
|
|
data['fuel_type']?.toString() ?? fuelTypeController.text;
|
|
registrationDateController.text =
|
|
data['date_of_registration']?.toString() ??
|
|
registrationDateController.text;
|
|
yomController.text =
|
|
data['year_of_manufacture']?.toString() ?? yomController.text;
|
|
|
|
engineNoController.text =
|
|
data['engine_no']?.toString() ?? engineNoController.text;
|
|
chassisNoController.text =
|
|
data['chassis_no']?.toString() ?? chassisNoController.text;
|
|
makeController.text = data['make']?.toString() ?? makeController.text;
|
|
modelController.text =
|
|
data['model']?.toString() ?? modelController.text;
|
|
cubicCapacityController.text =
|
|
data['cubic_capacity']?.toString() ??
|
|
cubicCapacityController.text;
|
|
vehicleTypeController.text =
|
|
data['vehicle_type']?.toString() ?? vehicleTypeController.text;
|
|
|
|
brokerNameController.text =
|
|
data['broker_name']?.toString() ?? brokerNameController.text;
|
|
commissionAmountController.text =
|
|
data['commission_amount']?.toString() ??
|
|
commissionAmountController.text;
|
|
});
|
|
} else {
|
|
debugPrint(
|
|
'findPolicyApi returned error: ${response?['message'] ?? 'unknown'}',
|
|
);
|
|
}
|
|
} catch (e, st) {
|
|
debugPrint('Exception in getFindPolicy: $e\n$st');
|
|
} finally {
|
|
if (!mounted) return;
|
|
setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _pickDate(
|
|
TextEditingController controller,
|
|
BuildContext context,
|
|
) async {
|
|
DateTime initial = DateTime.now();
|
|
try {
|
|
if (controller.text.isNotEmpty) {
|
|
// try parse dd-MM-yyyy
|
|
final parts = controller.text.split('-');
|
|
if (parts.length == 3) {
|
|
final d = int.tryParse(parts[0]) ?? initial.day;
|
|
final m = int.tryParse(parts[1]) ?? initial.month;
|
|
final y = int.tryParse(parts[2]) ?? initial.year;
|
|
initial = DateTime(y, m, d);
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
final picked = await showDatePicker(
|
|
context: context,
|
|
initialDate: initial,
|
|
firstDate: DateTime(1900),
|
|
lastDate: DateTime(2100),
|
|
);
|
|
if (picked != null) {
|
|
controller.text =
|
|
"${picked.day.toString().padLeft(2, '0')}-${picked.month.toString().padLeft(2, '0')}-${picked.year}";
|
|
}
|
|
}
|
|
|
|
Future<void> _save() async {
|
|
final policyId = widget.item?['policy_id'];
|
|
|
|
if (policyId == null) {
|
|
ToastHelper.showWarningToast(context, 'Missing policy id');
|
|
return;
|
|
}
|
|
|
|
if (!_formKey.currentState!.validate()) {
|
|
ToastHelper.showWarningToast(context, "Please fill all required fields");
|
|
return;
|
|
}
|
|
|
|
if (widget.item?['is_data_accuracy_checked'] == "1") {
|
|
ToastHelper.showWarningToast(context, "Data already confirmed");
|
|
return;
|
|
}
|
|
|
|
final payload = {
|
|
"id": policyId,
|
|
"rc_no": rcNoController.text,
|
|
"insured_name": insuredNameController.text,
|
|
"issued_date": issuedDateController.text,
|
|
"start_date": startDateController.text,
|
|
"end_date": endDateController.text,
|
|
"premium_amount": premiumController.text,
|
|
"policy_number": policyNumberController.text,
|
|
"tp": tpController.text.isEmpty ? null : tpController.text,
|
|
"od": odController.text.isEmpty ? null : odController.text,
|
|
"pa": paController.text.isEmpty ? null : paController.text,
|
|
"cgst": cgstController.text.isEmpty ? null : cgstController.text,
|
|
"sgst": sgstController.text.isEmpty ? null : sgstController.text,
|
|
"igst": igstController.text.isEmpty ? null : igstController.text,
|
|
"rto_state_code": rtoStateCodeController.text.isEmpty
|
|
? null
|
|
: rtoStateCodeController.text,
|
|
"rto_city_code": rtoCityCodeController.text.isEmpty
|
|
? null
|
|
: rtoCityCodeController.text,
|
|
"weight": weightController.text.isEmpty ? null : weightController.text,
|
|
"fuel_type": fuelTypeController.text.isEmpty
|
|
? null
|
|
: fuelTypeController.text,
|
|
"date_of_registration": registrationDateController.text.isEmpty
|
|
? null
|
|
: registrationDateController.text,
|
|
"year_of_manufacture": yomController.text.isEmpty
|
|
? null
|
|
: yomController.text,
|
|
"engine_no": engineNoController.text.isEmpty
|
|
? null
|
|
: engineNoController.text,
|
|
"chassis_no": chassisNoController.text.isEmpty
|
|
? null
|
|
: chassisNoController.text,
|
|
"make": makeController.text.isEmpty ? null : makeController.text,
|
|
"model": modelController.text.isEmpty ? null : modelController.text,
|
|
"cubic_capacity": cubicCapacityController.text.isEmpty
|
|
? null
|
|
: cubicCapacityController.text,
|
|
"vehicle_type": vehicleTypeController.text.isEmpty
|
|
? null
|
|
: vehicleTypeController.text,
|
|
"broker_name": brokerNameController.text.isEmpty
|
|
? null
|
|
: brokerNameController.text,
|
|
"commission_amount": commissionAmountController.text.isEmpty
|
|
? null
|
|
: commissionAmountController.text,
|
|
"updated_by": userId,
|
|
};
|
|
|
|
if (!mounted) return;
|
|
setState(() => isLoading = true);
|
|
|
|
try {
|
|
debugPrint('FINAL SAVE PAYLOAD => $payload');
|
|
final result = await apiService.updatePolicyApi(payload);
|
|
debugPrint('updatePolicy result => $result');
|
|
|
|
if (result != null && result['status'] == 'success') {
|
|
if (!mounted) return;
|
|
setState(() => isLoading = false);
|
|
context.pop();
|
|
ToastHelper.showSuccessToast(context, "Policy updated successfully");
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
// Now safe to do navigation + provider updates
|
|
final container = ProviderScope.containerOf(context);
|
|
container.read(policyDataAcurancyRefreshProvider.notifier).state =
|
|
true;
|
|
});
|
|
} else {
|
|
if (!mounted) return;
|
|
setState(() => isLoading = false);
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
result?['message']?.toString() ?? 'Error updating policy',
|
|
);
|
|
}
|
|
} catch (e, st) {
|
|
debugPrint('Exception in updatePolicy: $e\n$st');
|
|
if (!mounted) return;
|
|
setState(() => isLoading = false);
|
|
ToastHelper.showWarningToast(context, 'Exception: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> _fetchCommision() async {
|
|
final policyId = widget.item?['policy_id'];
|
|
|
|
if (policyId == null) {
|
|
ToastHelper.showWarningToast(context, 'Missing policy id');
|
|
return;
|
|
}
|
|
|
|
if (!_formKey.currentState!.validate()) {
|
|
ToastHelper.showWarningToast(context, "Please fill all required fields");
|
|
return;
|
|
}
|
|
|
|
// if (widget.item?['is_data_accuracy_checked'] == "1") {
|
|
// ToastHelper.showWarningToast(context, "Data already confirmed");
|
|
// return;
|
|
// }
|
|
|
|
final payload = {
|
|
"id": policyId,
|
|
"rc_no": rcNoController.text,
|
|
"insured_name": insuredNameController.text,
|
|
"issued_date": issuedDateController.text,
|
|
"start_date": startDateController.text,
|
|
"end_date": endDateController.text,
|
|
"premium_amount": premiumController.text,
|
|
"policy_number": policyNumberController.text,
|
|
"tp": tpController.text.isEmpty ? null : tpController.text,
|
|
"od": odController.text.isEmpty ? null : odController.text,
|
|
"pa": paController.text.isEmpty ? null : paController.text,
|
|
"cgst": cgstController.text.isEmpty ? null : cgstController.text,
|
|
"sgst": sgstController.text.isEmpty ? null : sgstController.text,
|
|
"igst": igstController.text.isEmpty ? null : igstController.text,
|
|
"rto_state_code": rtoStateCodeController.text.isEmpty
|
|
? null
|
|
: rtoStateCodeController.text,
|
|
"rto_city_code": rtoCityCodeController.text.isEmpty
|
|
? null
|
|
: rtoCityCodeController.text,
|
|
"weight": weightController.text.isEmpty ? null : weightController.text,
|
|
"fuel_type": fuelTypeController.text.isEmpty
|
|
? null
|
|
: fuelTypeController.text,
|
|
"date_of_registration": registrationDateController.text.isEmpty
|
|
? null
|
|
: registrationDateController.text,
|
|
"year_of_manufacture": yomController.text.isEmpty
|
|
? null
|
|
: yomController.text,
|
|
"engine_no": engineNoController.text.isEmpty
|
|
? null
|
|
: engineNoController.text,
|
|
"chassis_no": chassisNoController.text.isEmpty
|
|
? null
|
|
: chassisNoController.text,
|
|
"make": makeController.text.isEmpty ? null : makeController.text,
|
|
"model": modelController.text.isEmpty ? null : modelController.text,
|
|
"cubic_capacity": cubicCapacityController.text.isEmpty
|
|
? null
|
|
: cubicCapacityController.text,
|
|
"vehicle_type": vehicleTypeController.text.isEmpty
|
|
? null
|
|
: vehicleTypeController.text,
|
|
"broker_name": brokerNameController.text.isEmpty
|
|
? null
|
|
: brokerNameController.text,
|
|
// "commission_amount": commissionAmountController.text.isEmpty
|
|
// ? null
|
|
// : commissionAmountController.text,
|
|
"updated_by": userId,
|
|
// "insurance_plan_type":
|
|
};
|
|
|
|
if (!mounted) return;
|
|
setState(() => isLoading = true);
|
|
|
|
try {
|
|
debugPrint('FINAL SAVE PAYLOAD => $payload');
|
|
final result = await apiService.calculateCommissionRequest(payload);
|
|
debugPrint('updatePolicy result => $result');
|
|
|
|
if (result != null && result['status'] == 'success') {
|
|
if (!mounted) return;
|
|
setState(() => isLoading = false);
|
|
// context.pop();
|
|
ToastHelper.showSuccessToast(context, "Policy updated successfully");
|
|
|
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
// // Now safe to do navigation + provider updates
|
|
// final container = ProviderScope.containerOf(context);
|
|
// container.read(policyDataAcurancyRefreshProvider.notifier).state =
|
|
// true;
|
|
// });
|
|
} else {
|
|
if (!mounted) return;
|
|
setState(() => isLoading = false);
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
result?['message']?.toString() ?? 'Error updating policy',
|
|
);
|
|
}
|
|
} catch (e, st) {
|
|
debugPrint('Exception in updatePolicy: $e\n$st');
|
|
if (!mounted) return;
|
|
setState(() => isLoading = false);
|
|
ToastHelper.showWarningToast(context, 'Exception: $e');
|
|
}
|
|
}
|
|
|
|
Widget _sectionHeader(String title, {IconData icon = Icons.info}) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
|
|
decoration: BoxDecoration(
|
|
color: Color(0xFFE3F9F8),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(6),
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xFF2E7D6E),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(icon, size: 16, color: Colors.white),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Text(
|
|
title,
|
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInput(
|
|
String label,
|
|
TextEditingController controller, {
|
|
TextInputType keyboardType = TextInputType.text,
|
|
bool readOnly = false,
|
|
VoidCallback? onTap,
|
|
bool required = false,
|
|
List<TextInputFormatter>? inputFormatters, // 👈 ADD THIS
|
|
}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label, style: const TextStyle(fontWeight: FontWeight.w500)),
|
|
const SizedBox(height: 6),
|
|
TextFormField(
|
|
controller: controller,
|
|
keyboardType: keyboardType,
|
|
readOnly: readOnly,
|
|
onTap: onTap,
|
|
inputFormatters: inputFormatters, // 👈 USE HERE
|
|
validator: (value) {
|
|
if (required && (value == null || value.trim().isEmpty)) {
|
|
return "$label is required";
|
|
}
|
|
return null;
|
|
},
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(6)),
|
|
isDense: true,
|
|
|
|
// alignment fix
|
|
helperText: ' ',
|
|
helperStyle: const TextStyle(height: 1),
|
|
errorMaxLines: 2,
|
|
|
|
errorBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(6),
|
|
borderSide: const BorderSide(color: Colors.red),
|
|
),
|
|
focusedErrorBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(6),
|
|
borderSide: const BorderSide(color: Colors.red),
|
|
),
|
|
contentPadding: const EdgeInsets.all(10),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Ensure global AppHeader remains visible by pushing content down by 64px
|
|
return SafeArea(
|
|
child: Scaffold(
|
|
backgroundColor: Color(0xffD9EBE8),
|
|
// ⭐ FIXED BUTTON FOOTER
|
|
bottomNavigationBar: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
color: const Color(0xFFF7FBFA),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.end, // 👉 move button to right
|
|
children: [
|
|
ElevatedButton(
|
|
onPressed: _fetchCommision,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF2E7D6E),
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 10,
|
|
horizontal: 20, // 👉 smaller width
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
child: const Text(
|
|
'Fetch Commission',
|
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
SizedBox(width: 10),
|
|
ElevatedButton(
|
|
onPressed: widget.item?['is_data_accuracy_checked'] == '1'
|
|
? null
|
|
: _save,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF2E7D6E),
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 10,
|
|
horizontal: 20, // 👉 smaller width
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
child: const Text(
|
|
'Confirm Data Accuracy',
|
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
body: Container(
|
|
// margin: const EdgeInsets.only(top: 64), // <-- keeps popup under global AppHeader
|
|
width: double.infinity,
|
|
// height: MediaQuery.of(context).size.height - 64,
|
|
color: const Color(0xFFF7FBFA),
|
|
child: Column(
|
|
children: [
|
|
// In-body header
|
|
Container(
|
|
height: 56,
|
|
color: Color(0xFFE3F9F8),
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
child: Row(
|
|
children: [
|
|
IconButton(
|
|
icon: const Icon(Icons.file_open),
|
|
onPressed: () => context.pop(),
|
|
),
|
|
const Expanded(
|
|
child: Text(
|
|
'Insurance Policy Review',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
if (isLoading)
|
|
const SizedBox(
|
|
width: 12,
|
|
height: 12,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
),
|
|
const SizedBox(width: 8),
|
|
IconButton(
|
|
icon: const Icon(Icons.close),
|
|
onPressed: () => context.pop(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Body: left PDF preview + right grouped form
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Row(
|
|
children: [
|
|
// LEFT - PDF placeholder
|
|
Expanded(
|
|
flex: 1,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(right: 12),
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
const Text(
|
|
'Original Policy Document',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
Expanded(
|
|
child: FutureBuilder<Uint8List>(
|
|
future: loadNetworkPdfBytes(pdfUrl!),
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData) {
|
|
return const Center(
|
|
child: CircularProgressIndicator(),
|
|
);
|
|
}
|
|
|
|
final pdfBytes = snapshot.data!;
|
|
|
|
return PdfDocumentLoader.openData(
|
|
pdfBytes,
|
|
documentBuilder:
|
|
(context, pdfDocument, pageCount) {
|
|
return ListView.builder(
|
|
itemCount: pageCount,
|
|
itemBuilder: (context, index) {
|
|
return Container(
|
|
color: Colors.black12,
|
|
child: PdfPageView(
|
|
pdfDocument: pdfDocument,
|
|
pageNumber: index + 1,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
// Expanded(
|
|
// child: PdfDocumentLoader.openAsset(
|
|
// 'assets/pdfs/sample.pdf',
|
|
// documentBuilder: (context, pdfDocument, pageCount) => LayoutBuilder(
|
|
// builder: (context, constraints) => ListView.builder(
|
|
// itemCount: pageCount,
|
|
// itemBuilder: (context, index) => Container(
|
|
// // margin: EdgeInsets.all(margin),
|
|
// // padding: EdgeInsets.all(padding),
|
|
// color: Colors.black12,
|
|
// child: PdfPageView(
|
|
// pdfDocument: pdfDocument,
|
|
// pageNumber: index + 1,
|
|
// )
|
|
// )
|
|
// )
|
|
// ),
|
|
// )
|
|
// )
|
|
],
|
|
),
|
|
),
|
|
),
|
|
// RIGHT - Grouped Form
|
|
Expanded(
|
|
flex: 1,
|
|
child: SingleChildScrollView(
|
|
child: Form(
|
|
key: _formKey,
|
|
autovalidateMode:
|
|
AutovalidateMode.onUserInteraction,
|
|
child: Card(
|
|
elevation: 2,
|
|
color: Colors.white,
|
|
margin: EdgeInsets.zero,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Policy Details
|
|
Card(
|
|
elevation: 5,
|
|
color: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.stretch,
|
|
children: [
|
|
_sectionHeader(
|
|
'Policy Details',
|
|
icon: Icons.policy,
|
|
),
|
|
SizedBox(height: 10),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Policy Number',
|
|
policyNumberController,
|
|
required: true,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Insured Name',
|
|
insuredNameController,
|
|
required: true,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Issued Date',
|
|
issuedDateController,
|
|
readOnly: true,
|
|
required: true,
|
|
onTap: () => _pickDate(
|
|
issuedDateController,
|
|
context,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Start Date',
|
|
startDateController,
|
|
readOnly: true,
|
|
required: true,
|
|
onTap: () => _pickDate(
|
|
startDateController,
|
|
context,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'End Date',
|
|
endDateController,
|
|
readOnly: true,
|
|
required: true,
|
|
onTap: () => _pickDate(
|
|
endDateController,
|
|
context,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Premium Amount',
|
|
premiumController,
|
|
required: true,
|
|
keyboardType:
|
|
TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters:
|
|
decimalFormatter,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Remarks',
|
|
remarksController,
|
|
required: false,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// Tax Details
|
|
Card(
|
|
elevation: 5,
|
|
color: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.stretch,
|
|
children: [
|
|
_sectionHeader(
|
|
'Tax / Cover Details',
|
|
icon: Icons.attach_money,
|
|
),
|
|
SizedBox(height: 10),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInput(
|
|
'TP',
|
|
tpController,
|
|
required: true,
|
|
keyboardType:
|
|
TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters:
|
|
decimalFormatter,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'OD',
|
|
odController,
|
|
required: true,
|
|
keyboardType:
|
|
TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters:
|
|
decimalFormatter,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'PA',
|
|
paController,
|
|
required: true,
|
|
keyboardType:
|
|
TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters:
|
|
decimalFormatter,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInput(
|
|
'CGST',
|
|
cgstController,
|
|
required: true,
|
|
keyboardType:
|
|
TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters:
|
|
decimalFormatter,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'SGST',
|
|
sgstController,
|
|
required: true,
|
|
keyboardType:
|
|
TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters:
|
|
decimalFormatter,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'IGST',
|
|
igstController,
|
|
required: true,
|
|
keyboardType:
|
|
TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters:
|
|
decimalFormatter,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// Vehicle Details
|
|
Card(
|
|
elevation: 5,
|
|
color: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.stretch,
|
|
children: [
|
|
_sectionHeader(
|
|
'Vehicle Details',
|
|
icon: Icons.directions_car,
|
|
),
|
|
SizedBox(height: 10),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInput(
|
|
'RTO State Name',
|
|
rtoStateCodeController,
|
|
required: false,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'RTO City Code',
|
|
rtoCityCodeController,
|
|
required: false,
|
|
keyboardType:
|
|
TextInputType.number,
|
|
inputFormatters:
|
|
digitsOnlyFormatter,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Weight',
|
|
weightController,
|
|
required: false,
|
|
keyboardType:
|
|
TextInputType.number,
|
|
inputFormatters:
|
|
digitsOnlyFormatter,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Fuel Type',
|
|
fuelTypeController,
|
|
required: false,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Date of Registration',
|
|
registrationDateController,
|
|
required: false,
|
|
readOnly: true,
|
|
onTap: () => _pickDate(
|
|
registrationDateController,
|
|
context,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Year of Manufacture',
|
|
yomController,
|
|
required: false,
|
|
keyboardType:
|
|
TextInputType.number,
|
|
inputFormatters:
|
|
digitsOnlyFormatter,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Engine No',
|
|
engineNoController,
|
|
required: false,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Chassis No',
|
|
chassisNoController,
|
|
required: false,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Expanded(
|
|
child: _buildInput(
|
|
'Make',
|
|
makeController,
|
|
required: false,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Model',
|
|
modelController,
|
|
required: false,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Cubic Capacity',
|
|
cubicCapacityController,
|
|
required: false,
|
|
keyboardType:
|
|
TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters:
|
|
decimalFormatter,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Vehicle Type',
|
|
vehicleTypeController,
|
|
required: false,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// Broker Details
|
|
Card(
|
|
elevation: 5,
|
|
color: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.stretch,
|
|
children: [
|
|
_sectionHeader(
|
|
'Broker / Commission',
|
|
icon: Icons.person,
|
|
),
|
|
SizedBox(height: 10),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Broker Name',
|
|
brokerNameController,
|
|
required: false,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: _buildInput(
|
|
'Commission Amount',
|
|
commissionAmountController,
|
|
required: false,
|
|
keyboardType:
|
|
TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters:
|
|
decimalFormatter,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(
|
|
height: 20,
|
|
), // spacing above fixed button
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|