import 'dart:convert'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:nhance_partner/data/utils/toastNotification.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 '../../../../../data/services/auth_service.dart'; import '../../../../../data/utils/validators.dart'; import '../../../../layouts/responsive_layout.dart'; import '../../../../providers/manager_provider.dart'; import '../../../../providers/quotation_staff_proivder.dart'; import '../../../../themes/indicators/customizd_file_upload.dart'; import '../../../../themes/indicators/input_field_decoration.dart'; import '../../../../themes/indicators/text_field_theme.dart'; class CreateQuotationForm extends ConsumerStatefulWidget { final dynamic userId; final dynamic managerId; final dynamic selectedEnquiryId; final dynamic selectedInsurdId; final dynamic selectedQuotationFrmListdata; final dynamic selectedQuotationFrmListId; final void Function(String value) onSubmit; const CreateQuotationForm({ super.key, required this.userId, required this.managerId, required this.selectedEnquiryId, required this.selectedInsurdId, this.selectedQuotationFrmListdata, this.selectedQuotationFrmListId, required this.onSubmit, }); @override ConsumerState createState() => CreateQuotationFormState(); // State createState() => _CreateQuotationFormState(); } class CreateQuotationFormState extends ConsumerState { late ApiService apiService; String? _token; String? selectedFileNames; List> getInsurersData = []; List> filteredInsurersData = []; String? selectedInsurer; bool isLoading = false; bool _autoValidate = false; late TextEditingController controller; Map controllers = {}; final _formKey = GlobalKey(); String? docUploadedFileUrlFromApi; String? selectedId; PlatformFile? docUploadedFile; final GlobalKey>> dropDownKey = GlobalKey>>(); final GlobalKey>> dropDownKeyInsurer = GlobalKey>>(); List tabHeader = ['idv', 'premium_Amount', 'insurer']; List> getInsuranceTypeData = []; List> filteredInsuranceData = []; String? selectedInsPlanType; String? selectedEndorsement; Map dataDetails() { final data = { "enquiry_id": widget.selectedEnquiryId, "insured_declared_value": controllers["idv"]?.text, "insurer_id": widget.selectedInsurdId, // "insurer_id": selectedInsurer, "premium_amount": controllers["premium_Amount"]?.text, "insurance_plan_type_id": selectedInsPlanType, // "additional_uploaded_file_name": "extra_doc.pdf", // "created_by": widget.userId, "manager_id": widget.managerId, }; return data; } @override void initState() { super.initState(); apiService = ApiService(); for (String field in tabHeader) { controllers[field] = TextEditingController(); } // 🔹 Init logic here (API calls, token fetch, etc.) _initializeToken(); getInsuranceType(); // getInsurers(); updateData(); } Future _initializeToken() async { _token = await AuthService.getToken(); print("APISERTOKEN - $_token"); } @override void dispose() { // Dispose all TextEditingControllers for (var controller in controllers.values) { controller.dispose(); } super.dispose(); } void reset() { print('RESET'); _formKey.currentState?.reset(); // Clear all TextEditingControllers for (var controller in controllers.values) { controller.clear(); } dropDownKey.currentState?.changeSelectedItem(null); dropDownKeyInsurer.currentState?.changeSelectedItem(null); // Reset dropdowns / selections selectedInsurer = null; selectedInsPlanType = null; selectedEndorsement = null; // Reset file selection selectedFileNames = null; docUploadedFile = null; docUploadedFileUrlFromApi = null; // Reset selected ID selectedId = null; // Trigger UI update setState(() {}); } void updateData() { if (widget.selectedQuotationFrmListId != null && widget.selectedQuotationFrmListdata!.isNotEmpty) { print('checkData -- ${widget.selectedQuotationFrmListdata}'); selectedId = widget.selectedQuotationFrmListId!; // Pre-fill controllers controllers["idv"]?.text = widget.selectedQuotationFrmListdata!['insured_declared_value'] ?.toString() ?? ''; controllers["premium_Amount"]?.text = widget.selectedQuotationFrmListdata!['premium_amount']?.toString() ?? ''; selectedInsPlanType = widget .selectedQuotationFrmListdata!['insurance_plan_type_id'] ?.toString(); selectedInsurer = '1'; // controllers["insurer"]?.text = 'LIC'; // selectedInsurer = // widget.selectedQuotationFrmListdata!['insurer_id']?.toString() ?? ''; String? apiDocPath = widget.selectedQuotationFrmListdata!["additional_uploaded_file_name"]; if (apiDocPath != null && apiDocPath.isNotEmpty) { print('apiDocPath - $apiDocPath'); selectedFileNames = apiDocPath.split('/').last; print('selectedFileNames - $selectedFileNames'); docUploadedFileUrlFromApi = apiDocPath; print('passportFileUrlFromApi - $docUploadedFileUrlFromApi'); docUploadedFile = null; } else { selectedFileNames = null; docUploadedFile = null; docUploadedFileUrlFromApi = null; } } } Future 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>.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 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>.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; }); } } void handleDone() { if (!_formKey.currentState!.validate()) return; // setState(() { // _autoValidate = true; // enable autovalidation after first save attempt // }); setState(() { if (_formKey.currentState!.validate()) { dataDetails(); final dataSet = dataDetails(); print("dataSetAgent - $dataSet"); // print("managerId - $managerId ,userId - $userId "); createUserData(dataSet); } else { // isDi sable = false; } }); } Future createUserData(Map userData) async { // final bool isUpdating = widget.selectedQuotationFrmListId != null; // final id = widget.selectedQuotationFrmListId!; final bool isUpdating = widget.selectedQuotationFrmListId != null; final id = widget.selectedQuotationFrmListId; // keep nullable print('id - $id'); final uri = Uri.parse( isUpdating ? '${Env.apiUrl}quotation/updateQuotation' : '${Env.apiUrl}quotation/createQuotation', ); 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) { print('Updatrinf'); // request.fields['_method'] = 'PUT'; request.fields['id'] = id!; request.fields['updated_by'] = widget.userId!.toString(); } else { print('Not Updatrinf'); request.fields['created_by'] = widget.userId!.toString(); } 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 != 'certificate_file_name') { request.fields[key] = value.toString(); print("✅ Encoded $key: ${request.fields[key]}"); // } }); 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(); print("✅ Agent submitted successfully!"); print("Response: ${response.body}"); widget.onSubmit('Success'); reset(); // Navigator.of(context).pop(); } 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, 'Proposal Creation Failed'); } } catch (e) { print("🔥 Error submitting user: $e"); } } @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ // Header row // Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ // Text( // widget.selectedQuotationFrmListId != null // ? 'Update Proposal' // : 'Create Proposal', // style: GoogleFonts.inter( // color: const Color(0xFF374141), // fontSize: 14, // fontWeight: FontWeight.w600, // ), // ), // ], // ), // // const SizedBox(height: 8), // 🔹 Switch content dynamically buildFormFields(context), ], ); } Widget buildFormFields(BuildContext context) { return Form( key: _formKey, // autovalidateMode: AutovalidateMode.onUserInteraction, // autovalidateMode: _autoValidate // ? AutovalidateMode.onUserInteraction // : AutovalidateMode.disabled, child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ buildIdv(context), Spacer(), // buildInsurer(context), buildInsurancePlanType(context), Spacer(), buildPremiumAmnt(context), Spacer(), // buildDocuments(context), GestureDetector( onTap: () { handleDone(); // widget.onSubmit(controller.text.trim()); // Navigator.of(context).pop(); }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 25.0, vertical: 15, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8.0), color: const Color(0xFF425B5B), ), child: const Text( 'Save', style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600, ), ), ), ), // if (!ResponsiveLayout.isMobile(context)) const SizedBox(height: 10), // _buildResponsiveRow( // context, // buildAdditonalDocuments(context), // buildRemarks(context), // ), ], ), ); } // ------------------------- Claims Part ----------------------------------- Widget buildIdv(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('IDV *', style: _textStyle), SizedBox(height: 10), ThemedFormField( controller: controllers['idv']!, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[0-9.]')), ], validator: (value) => Validators.doubleNumber(value, "IDV"), // validator: (value) => Validators.number(value, "IDV"), backgroundColor: Color(0xFFEDF6F5), // readOnly: true, txtwidth: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.15, ), ], ); } Widget buildInsurer2(BuildContext context) { Map? selectedInsurerd = filteredInsurersData.firstWhere( (item) => item['id'].toString() == selectedInsurer, orElse: () => {}, ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Insurer *', style: _textStyle), SizedBox(height: 10), Container( decoration: BoxDecoration( color: Colors.white, // borderRadius: BorderRadius.circular(10.0), ), width: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.26, // height: 40, child: DropdownSearch>( key: dropDownKeyInsurer, selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null, items: (filter, infiniteScrollProps) { return filteredInsurersData; }, itemAsString: (val) => val['name'].toString(), // what to show compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Insurer", ).copyWith( filled: true, fillColor: Color( 0xFFEDF6F5, ), // 👈 makes the dropdown input white ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: "Search Insurer...", enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), // 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 buildPremiumAmnt(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Premium Amount *', style: _textStyle), SizedBox(height: 10), ThemedFormField( controller: controllers['premium_Amount']!, backgroundColor: Color(0xFFEDF6F5), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[0-9.]')), ], validator: (value) => Validators.doubleNumber(value, "PremiumAmount"), // validator: (value) => Validators.number(value, "PremiumAmount "), txtwidth: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.15, ), ], ); } Widget buildInsurancePlanType(context) { Map? selectedVehicle = filteredInsuranceData.firstWhere( (item) => item['id'].toString() == selectedInsPlanType, orElse: () => {}, ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Plan Type *", style: _textStyle), SizedBox(height: 10), Container( color: Colors.white, width: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.15, // height: 40, child: DropdownSearch>( key: dropDownKey, selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, items: (filter, infiniteScrollProps) { return filteredInsuranceData; }, itemAsString: (val) => val['insurance_plan_type'].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 Plan Type", ).copyWith( filled: true, fillColor: Color( 0xFFEDF6F5, ), // 👈 makes the dropdown input white ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: "Search Plan Type...", enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), // constraints: BoxConstraints(), ), onChanged: (val) { if (val != null) { print("Selected ClaimsType : ${val['insurance_plan_type']}"); print("Id: ${val['id']}"); selectedInsPlanType = val['id']; // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ), ], ); } Widget buildDocuments(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Proposal Documents ', style: _textStyle), const SizedBox(height: 10), Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ ThemedUploadField( hintText: selectedFileNames ?? "Upload Document", txtwidth: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.26, backgroundColor: Color(0xFFEDF6F5), onFileSelected: (fileName, file) { print("Picked file: $fileName (${file.size} bytes)"); setState(() { docUploadedFile = file; }); }, ), const SizedBox(height: 5), if (docUploadedFileUrlFromApi != null) Container( // color: Colors.white, child: Row( children: [ GestureDetector( onTap: () => apiService.downloadFile( apiUrl: 'quotation/downloadAdditionalUploadedFile?id=$selectedId', apiId: selectedId, localFile: docUploadedFile, fileName: selectedFileNames, ), child: Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), color: Color(0xFF425B5B), // color: Colors.green.shade300, ), 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), ], ), ), ), ], ), ), ], ), ], ); } // ------------------- STyle --------------------------------- static final TextStyle _textStyle = TextStyle( fontSize: 12, fontWeight: FontWeight.w600, ); }