import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:nhance_app_pwa/customAppBar/customAppBar.dart'; import 'package:nhance_app_pwa/pages/postEnrollment/service/api_service.dart'; import 'package:nhance_app_pwa/pages/service/SessionManager.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../config/environment.dart'; import '../../customAppBar/customFooter.dart'; import '../../customAppBar/responsive.dart'; import '../../customAppBar/tabs.dart'; import '../../customAppBar/toastHelper.dart'; import 'package:http/http.dart' as http; import '../service/TokenService.dart'; import '../service/popup_helper.dart'; import 'package:nhance_app_pwa/logger.dart'; class retailClaimForm extends StatefulWidget { final Map? details; const retailClaimForm({Key? key, this.details}) : super(key: key); @override State createState() => _retailClaimFormsState(); } class _retailClaimFormsState extends State { late ApiService apiService; bool isLoading = false; dynamic _token; dynamic empCodeString; dynamic empPrimaryId; dynamic client_id; Map? decodedToken; dynamic client_branch_id; dynamic mobileNo; dynamic policyTypeCondition; dynamic client_policy_id; dynamic emailId; String? selectedClaimTypeId; // claim type id to submit List claimTypeMasterList = []; // api list TextEditingController policyNoController = TextEditingController(); TextEditingController descriptionController = TextEditingController(); // Create a unique form key for each accordion section final GlobalKey formKey = GlobalKey(); bool isServiceValid = true; bool isPolicyValid = true; bool isSubjectValid = true; bool isMessageValid = true; final session = SessionManager(); @override void initState() { super.initState(); apiService = ApiService(context); // Initialize ApiService here descriptionController = TextEditingController(); policyNoController = TextEditingController(); // Retail policy details final retailData = widget.details?['retailDetails'] ?? {}; policyNoController.text = retailData['policy_no'] ?? ""; _loadToken(); } @override void dispose() { // Dispose the controllers to avoid memory leaks policyNoController.dispose(); descriptionController.dispose(); super.dispose(); } Future _loadToken() async { logDebug('_loadToken'); final String? token = await TokenService.getPostToken(); if (token != null && token.isNotEmpty) { setState(() { _token = token; }); // Decode the JWT token received from the API response decodedToken = Jwt.parseJwt(token); logDebug(decodedToken); mobileNo = session.mobileNo; client_branch_id = session.empClientBranchId; empCodeString = session.empCodeString; logDebug(empCodeString); // Check if emp_code is correct empPrimaryId = session.empPrimaryId; client_id = session.client_id; emailId = session.empEmailCorporate; logDebug(client_id); getClaimTypeMasterList(); } } Future getClaimTypeMasterList() async { setState(() => isLoading = true); try { final response = await apiService.getClaimTypeMasterApi(); if (response["status"] == "success") { setState(() { claimTypeMasterList = response["data"]; }); } } finally { setState(() => isLoading = false); } } Future submitRetailClaim() async { if (!formKey.currentState!.validate()) { ToastHelper.showErrorToast(context, "Please fill required fields"); return; } setState(() => isLoading = true); final retailData = widget.details?['retailDetails'] ?? {}; try { // ------------------------------ // Prepare data // ------------------------------ Map formData = { "policy_transaction_id": widget.details?['retailDetails']['policy_transaction_id'] ?? "", "policy_no": policyNoController.text, "policy_type_id": widget.details?['retailDetails']['policy_type_id'] ?? "", "claim_type":selectedClaimTypeId, "claim_description": descriptionController.text, }; logDebug("Retail Claim FormData: $formData"); // ------------------------------ // Prepare MultipartRequest // ------------------------------ final request = http.MultipartRequest( 'POST', Uri.parse("${Environment.apiUrl}/initiateClaim"), ); request.headers['Authorization'] = 'Bearer $_token'; request.headers['APP-SIGNATURE'] = 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y'; // Convert all values to String and add to fields final stringFields = formData.map( (key, value) => MapEntry(key, value?.toString() ?? ''), ); request.fields.addAll(stringFields); // ------------------------------ // Send request // ------------------------------ final response = await request.send(); final responseBody = await response.stream.bytesToString(); logDebug("Retail Claim API Response: $responseBody"); final decoded = jsonDecode(responseBody); // ------------------------------ // Handle Response // ------------------------------ if (decoded['status'] == true) { ToastHelper.showSuccessToast( context, decoded['message'] ?? "Claim submitted successfully!", ); // Clear form selectedClaimTypeId = null; descriptionController.clear(); setState(() => isLoading = false); // Redirect context.go("/claims"); } else { ToastHelper.showErrorToast( context, decoded['message'] ?? "Failed to submit claim", ); } } catch (e) { logDebug("Retail claim submit ERROR: $e"); ToastHelper.showErrorToast(context, "Unable to process. Please try again later"); } setState(() => isLoading = false); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) { if (didPop) return; context.go('/claims'); }, child: Scaffold( backgroundColor: Colors.white, appBar: CustomAppBar(), body: Stack(children: [ SingleChildScrollView( child: Container( padding: Responsive.isDesktop(context) ? EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.2, // 30% of screen width as horizontal padding vertical: MediaQuery.of(context).size.height * 0.05, // 5% of screen height as vertical padding ) : EdgeInsets.all(10), color: Colors.white, child: Column(children: [ Card( elevation: 5, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15.0), // Set border radius here ), child: Container( decoration: BoxDecoration( color: Colors.white, // Set background color to white borderRadius: BorderRadius.circular( 15.0), // Set border radius for Container ), padding: Responsive.isDesktop(context) ? EdgeInsets.only( top: 15, bottom: 15, left: 15, right: 15) : EdgeInsets.only( top: 10, bottom: 10, left: 10, right: 10), // Add padding to the container child: Row( children: [ Expanded( flex: 12, child: Container( alignment: Alignment.centerLeft, child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( decoration: BoxDecoration( color: Color( 0xFFFFFCE5), // Set background color for the container borderRadius: BorderRadius.circular( 10), // Set border radius for the container ), padding: Responsive.isDesktop(context) ? EdgeInsets.only( top: 20, bottom: 20, left: 0, right: 0) : EdgeInsets.only( top: 10, bottom: 10, left: 10, right: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( // flex: 1, child: InkWell( onTap: () { context.go('/claims'); }, child: Icon( Icons .chevron_left, // Replace with your desired icon color: Color(0xFF000000), size: 30, ), ), ), Expanded( flex: 11, child: Row( mainAxisAlignment: Responsive .isDesktop(context) ? MainAxisAlignment.start : MainAxisAlignment.start, children: [ Text( 'Claim Retail', textAlign: TextAlign.start, style: GoogleFonts.poppins( fontSize: Responsive .isDesktop( context) ? 20 : 16, fontWeight: FontWeight.w600, color: Color(0xFF000000), ), ) ], ), ) ], ), // Space between rows // Add more rows as needed ], ), ), SizedBox(height: 15), Container( child: Column( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, alignment: Alignment.centerLeft, child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Form( key: formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ---------------- Claim Type Dropdown ---------------- DropdownButtonFormField( value: selectedClaimTypeId, decoration: const InputDecoration(labelText: "Claim Type"), items: claimTypeMasterList.map>((item) { return DropdownMenuItem( value: item['id']?.toString() ?? "", // FIXED child: Text(item['claim_type']?.toString() ?? ""), // FIXED ); }).toList(), onChanged: (value) { setState(() => selectedClaimTypeId = value); }, validator: (value) => value == null || value.isEmpty ? "Please select a Claim Type" : null, ), const SizedBox(height: 20), // ---------------- Policy Number (readonly) ---------------- TextFormField( controller: policyNoController, readOnly: true, decoration: const InputDecoration( labelText: "Policy Number", ), validator: (value) { if (value == null || value.isEmpty) { return "Policy Number missing"; } return null; }, ), const SizedBox(height: 20), // ---------------- Claim Description ---------------- TextFormField( controller: descriptionController, maxLines: 5, decoration: const InputDecoration( labelText: "Claim Description", ), validator: (value) { if (value == null || value.trim().isEmpty) { return "Please enter the claim description"; } return null; }, ), const SizedBox(height: 25), // ---------------- Submit Button ---------------- Align( alignment: Alignment.centerRight, child: ElevatedButton( onPressed: () => submitRetailClaim(), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE26728), ), child: Text("Submit", style: GoogleFonts.poppins(color: Colors.white)), ), ), ], ), ) ], ), ), ], ), ], ), ), ], ))), ], ), ), ), SizedBox(height: Responsive.isDesktop(context) ? 40 : 60), ]), )), if (isLoading) Container( color: Color(0x98FFFCE5), // Semi-transparent background child: Center( child: // Your GIF loader widget Image.asset( height: 60, width: 60, 'assets/nhance-loader.gif'), // Adjust path to your GIF loader ), ), if (Responsive.isDesktop(context)) Align( alignment: Alignment.bottomCenter, child: Container( width: double.infinity, // Make the footer full width child: CustomFooter(), ), ), ]), // floatingActionButton: Responsive.isDesktop(context) // ? null // : FloatingActionButton( // onPressed: () { // Navigator.pushNamed(context, 'chatbot'); // }, // child: Icon(Icons.chat), // ), floatingActionButtonLocation: Responsive.isDesktop(context) ? null : FloatingActionButtonLocation.miniEndFloat, bottomNavigationBar: Responsive.isDesktop(context) ? null : CustomBottomNavigationBar( onTabChanged: (index) { // Add your navigation logic here // repositionBotman(); if (index == 0) { context.push('/home'); } else if (index == 1) { context.push('/claims'); } else if (index == 2) { context.push('/faqs'); } else if (index == 3) { context.push('/profile'); } else if (index == 4) { context.push('/help'); } // else if (index == 4) { // // context.push('/wellness'); // // Wellness tab clicked → show popup // if(!isRetailLoggedIn) // PopupHelper.showRedirectPopup( // context: context, // apiService: apiService, // empPrimaryId: session.empPrimaryId, // ); // } }, icons: [ Icons.home_outlined, Icons.sticky_note_2_outlined, Icons.question_answer_outlined, Icons.person_outline_outlined, Icons.headset_mic_outlined, ], labels: ["Home", "Claims", "FAQs", "Profile","Help"], initialIndex: 0, // Initial index of the bottom navigation bar ), )); } Widget buildTextField(String label, TextEditingController controller, [TextInputType keyboardType = TextInputType.text]) { return TextFormField( controller: controller, decoration: InputDecoration(labelText: label), keyboardType: keyboardType, maxLength: 150, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter the $label'; } return null; }, ); } Widget buildTextAreaField(String label, TextEditingController controller) { return TextFormField( controller: controller, decoration: InputDecoration(labelText: label), maxLines: 5, maxLength: 1500, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter the $label'; } return null; }, ); } Widget buildDropdownField( String label, void Function(T?) onChanged, bool readOnly, List> itemsList, String displayField, T? selectedValue, { String valueKey = 'id', String? combineField, }) { return DropdownButtonFormField( value: selectedValue, decoration: InputDecoration(labelText: label), items: itemsList.map>((item) { // ▼ If combineField is provided → show "policy_no - policy_type" String textToShow; if (combineField != null) { textToShow = "${item[displayField]} - ${item[combineField]}"; } else { textToShow = item[displayField].toString(); } return DropdownMenuItem( value: item[valueKey] as T, // ✅ cast to generic type child: Text(textToShow), // always display as String ); }).toList(), onChanged: readOnly ? null : onChanged, validator: (value) { if (value == null) { return 'Please select a $label'; } return null; }, ); } }