diff --git a/.env b/.env index 5f1f339..3c01a91 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ -API_URL=https://venbait.in/nhance/dev/employeeRest/ -chatbot_Url=https://venbait.in/nhance/dev/chatbot -API_URL_Enrollment=https://venbait.in/enrollment/employeeRest/ -TICKET_API_URL=https://venbait.in/nhance/dev/api -BASE_HREF=/nhance/app/dev/ -ENV=development +API_URL=https://app.nhanceindia.in/zenith/employeeRest/ +API_URL_Enrollment=https://app.nhanceindia.in/enrolment/employeeRest/ +TICKET_API_URL=https://app.nhanceindia.in/zenith/api +chatbot_Url=https://app.nhanceindia.in/zenith/chatbot +BASE_HREF=/app/ +ENV=production \ No newline at end of file diff --git a/.env.development b/.env.development index 5f1f339..3c01a91 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,6 @@ -API_URL=https://venbait.in/nhance/dev/employeeRest/ -chatbot_Url=https://venbait.in/nhance/dev/chatbot -API_URL_Enrollment=https://venbait.in/enrollment/employeeRest/ -TICKET_API_URL=https://venbait.in/nhance/dev/api -BASE_HREF=/nhance/app/dev/ -ENV=development +API_URL=https://app.nhanceindia.in/zenith/employeeRest/ +API_URL_Enrollment=https://app.nhanceindia.in/enrolment/employeeRest/ +TICKET_API_URL=https://app.nhanceindia.in/zenith/api +chatbot_Url=https://app.nhanceindia.in/zenith/chatbot +BASE_HREF=/app/ +ENV=production \ No newline at end of file diff --git a/assets/nhance_client_logo.png b/assets/nhance_client_logo.png old mode 100755 new mode 100644 diff --git a/assets/nhance_client_logo.svg b/assets/nhance_client_logo.svg new file mode 100644 index 0000000..1b3df04 --- /dev/null +++ b/assets/nhance_client_logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/nhance_logo.png b/assets/nhance_logo.png new file mode 100644 index 0000000..26029eb Binary files /dev/null and b/assets/nhance_logo.png differ diff --git a/lib/customAppBar/customAppBar.dart b/lib/customAppBar/customAppBar.dart index 30fa214..52d14e4 100755 --- a/lib/customAppBar/customAppBar.dart +++ b/lib/customAppBar/customAppBar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:adaptive_navbar/adaptive_navbar.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:nhance_app_pwa/customAppBar/responsive.dart'; @@ -59,14 +60,18 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { .start, // Adjust the alignment as needed children: [ Container( - margin: EdgeInsets.only( - top: 10, bottom: 10, left: 10, right: 10), - width: Responsive.isDesktop(context) ? 150 : 130, - height: Responsive.isDesktop(context) ? 150 : 130, - child: Image.asset( - 'assets/nhance_client_logo.png', - fit: BoxFit.contain, // Adjust the fit as needed + // margin: EdgeInsets.only( + // top: 10, bottom: 10, left: 10, right: 10), + // width: Responsive.isDesktop(context) ? 150 : 130, + // height: Responsive.isDesktop(context) ? 31 : 130, + child: SvgPicture.asset( + 'assets/nhance_client_logo.svg', + width: 150, ), + // child: Image.asset( + // 'assets/nhance_logo.png', + // width: 250, + // ), ), ], ), diff --git a/lib/pages/enrollment/addons.dart b/lib/pages/enrollment/addons.dart index dc99640..42e8674 100755 --- a/lib/pages/enrollment/addons.dart +++ b/lib/pages/enrollment/addons.dart @@ -85,6 +85,8 @@ class _addOnsDetailsState extends State { dynamic gmcTypeName; dynamic clientName; dynamic clientLogo; + bool gpahasPremiumSummary = false; + bool gmchasPremiumSummary = false; dynamic addon_subheading; dynamic topUpECardDownload; dynamic topUpMappedFamilyFloatersSi; @@ -332,6 +334,10 @@ class _addOnsDetailsState extends State { setState(() { gpaPolicies = response['data']; for (var item in gpaPolicies) { + if (item['is_premium_summery'] == "1") { + gpahasPremiumSummary = true; + break; + } // Extract disclaimer value from each object if (item['OpenForEnrollment'] == '1') { dynamic disclaimer = item['disclaimer']; @@ -408,6 +414,13 @@ class _addOnsDetailsState extends State { // setState(() { gmcPolicies = response['data']; for (var item in gmcPolicies) { + print('checking is_premium_summery ${item['is_premium_summery']}'); + if (item['is_premium_summery'] == "1") { + print('checking is_premium_summery ${item['is_premium_summery']}'); + gmchasPremiumSummary = true; + print('gmchasPremiumSummary ${gmchasPremiumSummary}'); + break; + } if (item['OpenForEnrollment'] == '1') { dynamic disclaimer = item['disclaimer']; if (disclaimer != null && disclaimer.isNotEmpty) { @@ -4056,217 +4069,220 @@ class _addOnsDetailsState extends State { : EdgeInsets.all(10), child: Column( children: [ - - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 12, - child: Container( - alignment: Alignment.centerLeft, - child: Column( - mainAxisAlignment: - MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - 'Premium Summary', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: - Responsive.isDesktop( - context) - ? 18 - : 16, - color: Color(0xFF181818), - fontWeight: FontWeight.w600, - ), - ), - ], - ))), - ], - ), - ), - SizedBox(height: 15), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 12, - child: Container( - alignment: Alignment - .centerLeft, // Adjust padding as needed - child: Table( - columnWidths: { - 0: FlexColumnWidth( - 2), // Adjust column width as needed - 1: FlexColumnWidth(2), - 2: FlexColumnWidth( - 2), // Adjust column width as needed - 3: FlexColumnWidth(2), - }, - children: [ - TableRow( + if(gpahasPremiumSummary == true || gmchasPremiumSummary == true)...[ + Container( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 12, + child: Container( + alignment: Alignment.centerLeft, + child: Column( + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ - TableCell( - child: Padding( - padding: - EdgeInsets.all(8.0), - child: Text( - 'Policy ', - textAlign: - TextAlign.start, - style: GoogleFonts.poppins( - fontSize: Responsive - .isDesktop( - context) - ? 16 - : 13, - fontWeight: - FontWeight - .bold), - ), - ), - ), - TableCell( - child: Padding( - padding: - EdgeInsets.all(8.0), - child: Text( - 'Additional Premium', - textAlign: - TextAlign.start, - style: GoogleFonts.poppins( - fontSize: Responsive - .isDesktop( - context) - ? 16 - : 13, - fontWeight: - FontWeight - .bold), - ), - ), - ), - TableCell( - child: Padding( - padding: - EdgeInsets.all(8.0), - child: Text( - 'GST', - textAlign: - TextAlign.start, - style: GoogleFonts.poppins( - fontSize: Responsive - .isDesktop( - context) - ? 16 - : 13, - fontWeight: - FontWeight - .bold), - ), - ), - ), - TableCell( - child: Padding( - padding: - EdgeInsets.all(8.0), - child: Text( - 'Total', - textAlign: - TextAlign.start, - style: GoogleFonts.poppins( - fontSize: Responsive - .isDesktop( - context) - ? 16 - : 13, - fontWeight: - FontWeight - .bold), - ), + Text( + 'Premium Summary', + textAlign: TextAlign.left, + style: GoogleFonts.poppins( + fontSize: + Responsive.isDesktop( + context) + ? 18 + : 16, + color: Color(0xFF181818), + fontWeight: FontWeight.w600, ), ), ], + ))), + ], + ), + ), + SizedBox(height: 15), + if(gpahasPremiumSummary == true || gmchasPremiumSummary == true) + Container( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 12, + child: Container( + alignment: Alignment + .centerLeft, // Adjust padding as needed + child: Table( + columnWidths: { + 0: FlexColumnWidth( + 2), // Adjust column width as needed + 1: FlexColumnWidth(2), + 2: FlexColumnWidth( + 2), // Adjust column width as needed + 3: FlexColumnWidth(2), + }, + children: [ + TableRow( + children: [ + TableCell( + child: Padding( + padding: + EdgeInsets.all(8.0), + child: Text( + 'Policy ', + textAlign: + TextAlign.start, + style: GoogleFonts.poppins( + fontSize: Responsive + .isDesktop( + context) + ? 16 + : 13, + fontWeight: + FontWeight + .bold), + ), + ), + ), + TableCell( + child: Padding( + padding: + EdgeInsets.all(8.0), + child: Text( + 'Additional Premium', + textAlign: + TextAlign.start, + style: GoogleFonts.poppins( + fontSize: Responsive + .isDesktop( + context) + ? 16 + : 13, + fontWeight: + FontWeight + .bold), + ), + ), + ), + TableCell( + child: Padding( + padding: + EdgeInsets.all(8.0), + child: Text( + 'GST', + textAlign: + TextAlign.start, + style: GoogleFonts.poppins( + fontSize: Responsive + .isDesktop( + context) + ? 16 + : 13, + fontWeight: + FontWeight + .bold), + ), + ), + ), + TableCell( + child: Padding( + padding: + EdgeInsets.all(8.0), + child: Text( + 'Total', + textAlign: + TextAlign.start, + style: GoogleFonts.poppins( + fontSize: Responsive + .isDesktop( + context) + ? 16 + : 13, + fontWeight: + FontWeight + .bold), + ), + ), + ), + ], + ), + if (gpaPolicies != null && + gpaPolicies.length > 0) + ...gpaBuildPolicyRows( + gpaPolicies, context), + if (gmcPolicies != null && + gmcPolicies.length > 0) + ...gmcBuildPolicyRows( + gmcPolicies, context), + ], ), - if (gpaPolicies != null && - gpaPolicies.length > 0) - ...gpaBuildPolicyRows( - gpaPolicies, context), - if (gmcPolicies != null && - gmcPolicies.length > 0) - ...gmcBuildPolicyRows( - gmcPolicies, context), - ], - ), - )) - ], - ), - ), - SizedBox(height: 10), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 6, - child: Container( - alignment: Alignment.centerLeft, - child: Column( - mainAxisAlignment: - MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - 'Total Payable', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: - Responsive.isDesktop( - context) - ? 20 - : 16, - color: Color(0xFF181818), - fontWeight: FontWeight.w600, + )) + ], + ), + ), + SizedBox(height: 10), + Container( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 6, + child: Container( + alignment: Alignment.centerLeft, + child: Column( + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Total Payable', + textAlign: TextAlign.left, + style: GoogleFonts.poppins( + fontSize: + Responsive.isDesktop( + context) + ? 20 + : 16, + color: Color(0xFF181818), + fontWeight: FontWeight.w600, + ), ), - ), - ], - ))), - Expanded( - flex: 6, - child: Container( - alignment: Alignment.centerRight, - child: Column( - mainAxisAlignment: - MainAxisAlignment.end, - crossAxisAlignment: - CrossAxisAlignment.end, - children: [ - Text( - '₹${allTotalSum()}/Year', - textAlign: TextAlign.right, - style: GoogleFonts.poppins( - fontSize: - Responsive.isDesktop( - context) - ? 18 - : 16, - color: Color(0xFFE26728), - fontWeight: FontWeight.w500, + ], + ))), + Expanded( + flex: 6, + child: Container( + alignment: Alignment.centerRight, + child: Column( + mainAxisAlignment: + MainAxisAlignment.end, + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Text( + '₹${allTotalSum()}/Year', + textAlign: TextAlign.right, + style: GoogleFonts.poppins( + fontSize: + Responsive.isDesktop( + context) + ? 18 + : 16, + color: Color(0xFFE26728), + fontWeight: FontWeight.w500, + ), ), - ), - ], - ))) - ], + ], + ))) + ], + ), ), - ), - SizedBox(height: 10), + SizedBox(height: 10), + ], + Container( alignment: Alignment.topLeft, child: Row( diff --git a/lib/pages/enrollment/empReview.dart b/lib/pages/enrollment/empReview.dart index f0e4aea..1c86992 100755 --- a/lib/pages/enrollment/empReview.dart +++ b/lib/pages/enrollment/empReview.dart @@ -510,8 +510,11 @@ class _empReviewDetailsState extends State { print('gmcPolicies $gmcPolicies'); for (var policy in gmcPolicies) { + print('checking is_premium_summery ${policy['is_premium_summery']}'); if (policy['is_premium_summery'] == "1") { + print('checking is_premium_summery ${policy['is_premium_summery']}'); gmchasPremiumSummary = true; + print('gmchasPremiumSummary ${gmchasPremiumSummary}'); break; } } diff --git a/lib/pages/postEnrollment/planclaimsform.dart b/lib/pages/postEnrollment/planclaimsform.dart index 8bee409..313a39c 100755 --- a/lib/pages/postEnrollment/planclaimsform.dart +++ b/lib/pages/postEnrollment/planclaimsform.dart @@ -115,6 +115,8 @@ class _planclaimsformState extends State { bool isClaimAmountValid = true; bool isAccidentDateValid = true; bool isIntimationDateValid = true; + bool isAdmitDateValid = true; + bool isDischargeDateValid = true; final session = SessionManager(); @@ -516,7 +518,9 @@ class _planclaimsformState extends State { isMemberValid = selectedMemberId != null; isSubjectValid = subjectController.text.trim().isNotEmpty; isHospitalNameValid = policyTypeCondition != 1 || hospitalNameController.text.trim().isNotEmpty; - isAdmitDischargeValid = policyTypeCondition != 1 || (admitDate != null && dischargeDate != null); + // isAdmitDischargeValid = policyTypeCondition != 1 || (admitDate != null && dischargeDate != null); + isAdmitDateValid = policyTypeCondition != 1 || admitDate != null; + isDischargeDateValid = policyTypeCondition != 1 || dischargeDate != null; isClaimAmountValid = serviceId != 1 || claimAmountController.text.trim().isNotEmpty; isAccidentDateValid = (serviceId == 2 || serviceId == 3 || serviceId == 4) ? accidentDate != null : true; isIntimationDateValid = (serviceId == 2 || serviceId == 3 || serviceId == 4) ? intimationDate != null : true; @@ -527,10 +531,18 @@ class _planclaimsformState extends State { isMemberValid && isSubjectValid && isHospitalNameValid && - isAdmitDischargeValid && + isAdmitDateValid && + isDischargeDateValid && isClaimAmountValid && isAccidentDateValid && isIntimationDateValid) { + if (FileUploadService().files.isEmpty) { + setState(() { + MultiFileUploadWidget.hasFiles = false; + }); + ToastHelper.showErrorToast(context, 'Please upload at least one document'); + return; + } // Proceed to submit } else { ToastHelper.showErrorToast(context, 'Please Fill Required Fields'); @@ -544,20 +556,17 @@ class _planclaimsformState extends State { final SharedPreferences prefs = await SharedPreferences.getInstance(); final String? emp_id = session.empPrimaryId; print('Check One $client_policy_id'); - // print(employeePolicyList[0]['name']); - // print('Help $filteredPoliciesList'); + try { print('Check One'); Map fields = { - // 'opener': 'user', + 'emp_id': emp_id, 'client_policy_id': client_policy_id, 'ticket_type_id': serviceId, 'subject': subjectController.text, 'message': messageController.text, - // 'policy_id': fromClaimsPage == 0 - // ? claimsDetails['client_policy_id'] - // : policyNumberId, + 'emp_code': empCodeString ?? ' ', 'mobile_number': mobileNo ?? '', 'email': session.empEmailCorporate ?? '', @@ -571,35 +580,16 @@ class _planclaimsformState extends State { : (fromClaimsPage == 0 ? claimsDetails['policy_name'] : filteredPoliciesList[0]['policy_name']), - // 'tpa': serviceId == 1 - // ? null - // : (fromClaimsPage == 0 - // ? claimsDetails['tpa_name'] - // : filteredPoliciesList[0]['tpa_name']), - // 'insurer': serviceId == 1 - // ? null - // : (fromClaimsPage == 0 - // ? claimsDetails['insurer_name'] - // : filteredPoliciesList[0]['insurer_name']), + }; - // Conditionally add fields based on the first word in policy_type - // if (policyTypeCondition == 3) { - // String formattedAccidentDate = - // DateFormat('yyyy-MM-dd').format(accidentDate!); - // // formData['accident_details'] = accidentDetailsController.text; - // formData['accident_details'] = messageController.text; - // formData['accident_date'] = formattedAccidentDate; - // formData['claim_amount'] = claimAmountController.text; - // formData['policyholder_name'] = employeePolicyList[0]['name']; - // formData['member_name'] = selectedMemberName; - // formData['emp_policy_id'] = selectedMemberId; - // } else if (policyTypeCondition == 1) { + String formattedAdmitDate = DateFormat('yyyy-MM-dd').format(admitDate!); + String formattedDischargeDate = DateFormat('yyyy-MM-dd').format(dischargeDate!); fields['hospital_name'] = hospitalNameController.text; - fields['doa'] = admitDateController.text; - fields['dod'] = dischargeDateController.text; + fields['doa'] = formattedAdmitDate; + fields['dod'] = formattedDischargeDate; fields['claim_amount'] = claimAmountController.text; fields['policyholder_name'] = employeePolicyList[0]['name']; fields['member_name'] = selectedMemberName; @@ -611,8 +601,6 @@ class _planclaimsformState extends State { String formattedBirthDate = DateFormat('yyyy-MM-dd').format(birthDate!); String formattedIntimationDate = DateFormat('yyyy-MM-dd').format(intimationDate!); - // formData['accident_details'] = accidentDetailsController.text; - // formData['accident_details'] = messageController.text; fields['date_of_accident'] = formattedAccidentDate; fields['dob'] = formattedBirthDate; fields['date_of_intimat'] = formattedIntimationDate; @@ -623,26 +611,6 @@ class _planclaimsformState extends State { fields['insured_emp_id'] = selectedMemberId; } - // Multipart request - - // final request = http.MultipartRequest('POST', Uri.parse('${Environment.apiUrl}/initiateClaim')); - // request.headers['Authorization'] = 'Bearer $_token'; - // final stringFields = fields.map((key, value) => MapEntry(key, value?.toString() ?? '')); - // request.fields.addAll(stringFields); - // - // if (uploadedFile != null) { - // final reader = html.FileReader(); - // reader.readAsArrayBuffer(uploadedFile!); - // await reader.onLoad.first; - // - // final data = reader.result as Uint8List; - // - // request.files.add(http.MultipartFile.fromBytes( - // 'claim_docs', - // data, - // filename: uploadedFile!.name, - // )); - // } _token = await TokenService.getPostToken(); final request = http.MultipartRequest('POST', Uri.parse('${Environment.apiUrl}/initiateClaim')); @@ -650,31 +618,44 @@ class _planclaimsformState extends State { final stringFields = fields.map((key, value) => MapEntry(key, value?.toString() ?? '')); request.fields.addAll(stringFields); - // 🔥 Loop through multiple files - // for (var file in uploadedFiles) { - // final reader = html.FileReader(); - // reader.readAsArrayBuffer(file); - // await reader.onLoad.first; - // final data = reader.result as Uint8List; - // - // request.files.add(http.MultipartFile.fromBytes( - // 'claim_docs[]', // 👈 if backend expects array - // data, - // filename: file.name, - // )); - // } - uploadedFiles = FileUploadService().files; - print("Files available globally: ${uploadedFiles.map((f) => f.name).toList()}"); - for (var file in uploadedFiles) { - if (file.bytes != null) { + // get uploaded files + final uploadedFiles = FileUploadService().files; + print("📁 Total uploaded files: ${uploadedFiles.length}"); + print("📂 File list (original names): ${uploadedFiles.map((f) => f.file.name).toList()}"); + +// Validate all labels present (optional but recommended) + for (final uf in uploadedFiles) { + print("📝 Checking label for file: ${uf.file.name}, current label: '${uf.label}'"); + if ((uf.label ?? '').trim().isEmpty) { + ToastHelper.showErrorToast(context, 'Please enter name for all uploaded documents'); + setState(() => isLoading = false); + return; + } + } + + // Add files + for (var uf in uploadedFiles) { + final pf = uf.file; + if (pf.bytes != null) { request.files.add(http.MultipartFile.fromBytes( 'claim_docs[]', - file.bytes!, - filename: file.name, + pf.bytes!, + filename: pf.name, )); } } +// ✅ Combine all names into a JSON array string + final claimDocNames = uploadedFiles.map((uf) => uf.label.trim()).toList(); + final encodedNames = jsonEncode(claimDocNames); + request.fields['claim_doc_names'] = encodedNames; + +// ✅ Debug print + print("Payload being sent:"); + print("Files: ${uploadedFiles.map((f) => f.file.name).toList()}"); + print("Names (JSON): $encodedNames"); + + final response = await request.send(); final responseBody = await response.stream.bytesToString(); @@ -1199,25 +1180,42 @@ SizedBox(height: 15), // accidentDetailsController), // SizedBox(height: 15), if (policyTypeCondition == 1) ...[ - InkWell( - onTap: () => _pickFromToDate(context), - child: InputDecorator( - decoration: InputDecoration( - labelText: 'Admit Date - Discharge Date', - border: OutlineInputBorder(), - ), - child: Text( - admitDate != null && dischargeDate != null - ? '${admitDateController.text} to ${dischargeDateController.text}' - : 'Select Admit and Discharge Dates', - style: TextStyle(color: Colors.black), - ), - ), + // 🟡 Admit Date — no future allowed + buildDatePickerField( + label: 'Admit Date', + selectedDate: admitDate, + allowFuture: false, + onDateSelected: (selectedDate) { + setState(() { + admitDate = selectedDate; + dischargeDate = null; // reset discharge when admit changes + }); + }, ), - if (!isAdmitDischargeValid) - Text('Please select Admit and Discharge Dates', style: TextStyle(color: Colors.red)), + if (!isAdmitDateValid) + const Text('Please select the Admit Date', style: TextStyle(color: Colors.red)), + const SizedBox(height: 20), + + // 🟢 Discharge Date — must be after Admit Date + buildDatePickerField( + label: 'Discharge Date', + selectedDate: dischargeDate, + allowFuture: true, + minDate: admitDate != null + ? admitDate!.add(const Duration(days: 1)) + : DateTime.now().add(const Duration(days: 1)), + maxDate: null, + onDateSelected: (selectedDate) { + setState(() { + dischargeDate = selectedDate; + }); + }, + ), + if (!isDischargeDateValid) + const Text('Please select the Discharge Date', style: TextStyle(color: Colors.red)), ], + SizedBox(height: 15), if (serviceId == 1) ...[ buildTextField('Claims Amount', claimAmountController, TextInputType.number), @@ -1421,14 +1419,42 @@ SizedBox(height: 15), return InkWell( onTap: () async { final DateTime now = DateTime.now(); - final DateTime first = minDate ?? DateTime(2000); - final DateTime last = allowFuture ? (maxDate ?? DateTime(2100)) : now; + + // 👇 Special rules only for Admit & Discharge + DateTime first; + DateTime last; + + if (label == 'Admit Date') { + // ❌ Admit date cannot be future + first = minDate ?? DateTime(2000); + last = now; + } else if (label == 'Discharge Date') { + // ✅ Discharge date must be AFTER admit date, future allowed + first = minDate ?? now.add(const Duration(days: 1)); + last = maxDate ?? DateTime(2100); + } else { + // All other date pickers keep original behavior + first = minDate ?? DateTime(1980); + last = allowFuture ? (maxDate ?? DateTime(2100)) : now; + } + + // final DateTime? pickedDate = await showDatePicker( + // context: context, + // initialDate: selectedDate ?? now, + // firstDate: label == "Date of Birth" ? DateTime(1900) : first, + // lastDate: label == "Date of Birth" ? now : last, + // ); + + // ✅ Fix: Ensure initialDate always within valid range + DateTime initialDate = selectedDate ?? now; + if (initialDate.isBefore(first)) initialDate = first; + if (initialDate.isAfter(last)) initialDate = last; final DateTime? pickedDate = await showDatePicker( context: context, - initialDate: selectedDate ?? now, - firstDate: label == "Date of Birth" ? DateTime(1900) : first, - lastDate: label == "Date of Birth" ? now : last, + initialDate: initialDate, + firstDate: first, + lastDate: last, ); if (pickedDate != null) { @@ -1438,18 +1464,19 @@ SizedBox(height: 15), child: InputDecorator( decoration: InputDecoration( labelText: label, - border: OutlineInputBorder(), + border: const OutlineInputBorder(), ), child: Text( selectedDate != null ? DateFormat('dd-MM-yyyy').format(selectedDate) : 'Select $label', - style: TextStyle(color: Colors.black), + style: const TextStyle(color: Colors.black), ), ), ); } + Widget buildDropdownField( String label, void Function(int?) onChanged, diff --git a/lib/pages/postEnrollment/service/file_upload_service.dart b/lib/pages/postEnrollment/service/file_upload_service.dart index ea1da68..643d002 100644 --- a/lib/pages/postEnrollment/service/file_upload_service.dart +++ b/lib/pages/postEnrollment/service/file_upload_service.dart @@ -1,16 +1,31 @@ import 'package:file_picker/file_picker.dart'; +import 'package:flutter/widgets.dart'; + +class UploadedFile { + final PlatformFile file; + final TextEditingController controller; + UploadedFile({required this.file}) + : controller = TextEditingController( + text: file.extension != null + ? file.name.replaceAll('.${file.extension}', '') + : file.name); + + String get label => controller.text; + void dispose() => controller.dispose(); +} class FileUploadService { FileUploadService._(); static final FileUploadService _instance = FileUploadService._(); factory FileUploadService() => _instance; - final List _files = []; + final List _files = []; - List get files => List.unmodifiable(_files); + // Expose as unmodifiable but containing UploadedFile objects + List get files => List.unmodifiable(_files); // Allowed extensions - static const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg','heic']; + static const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg', 'heic']; Future pickFiles({int maxFileSizeInMB = 3}) async { final result = await FilePicker.platform.pickFiles( @@ -21,26 +36,35 @@ class FileUploadService { ); if (result != null) { - for (final file in result.files) { - final ext = file.extension?.toLowerCase() ?? ''; - final sizeInMB = (file.size / (1024 * 1024)); + for (final pf in result.files) { + final ext = pf.extension?.toLowerCase() ?? ''; + final sizeInMB = (pf.size / (1024 * 1024)); if (!allowedExtensions.contains(ext)) { - return "Unsupported file format: ${file.name}"; + return "Unsupported file format: ${pf.name}"; } if (sizeInMB > maxFileSizeInMB) { - return "File too large (${file.name}). Max $maxFileSizeInMB MB allowed."; + return "File too large (${pf.name}). Max $maxFileSizeInMB MB allowed."; } - } - _files.addAll(result.files); + // Wrap PlatformFile in our UploadedFile model + _files.add(UploadedFile(file: pf)); + } } - return null; // success + return null; } void removeFileAt(int index) { - if (index >= 0 && index < _files.length) _files.removeAt(index); + if (index >= 0 && index < _files.length) { + _files[index].dispose(); + _files.removeAt(index); + } } - void clearAll() => _files.clear(); + void clearAll() { + for (final f in _files) { + f.dispose(); + } + _files.clear(); + } } diff --git a/lib/pages/postEnrollment/service/multi_file_upload_widget.dart b/lib/pages/postEnrollment/service/multi_file_upload_widget.dart index 3f3dc3b..7ae2df4 100644 --- a/lib/pages/postEnrollment/service/multi_file_upload_widget.dart +++ b/lib/pages/postEnrollment/service/multi_file_upload_widget.dart @@ -7,6 +7,8 @@ class MultiFileUploadWidget extends StatefulWidget { @override State createState() => _MultiFileUploadWidgetState(); + + static bool hasFiles = false; // ✅ Static variable to check from parent } class _MultiFileUploadWidgetState extends State { @@ -39,13 +41,16 @@ class _MultiFileUploadWidgetState extends State { } else { setState(() { errorMessage = null; + MultiFileUploadWidget.hasFiles = fileService.files.isNotEmpty; }); } } void _removeFile(int index) { fileService.removeFileAt(index); - setState(() {}); + setState(() { + MultiFileUploadWidget.hasFiles = fileService.files.isNotEmpty; + }); } @override @@ -108,6 +113,14 @@ class _MultiFileUploadWidgetState extends State { ), ], + if (fileService.files.isEmpty && errorMessage == null) ...[ + const SizedBox(height: 4), + const Text( + "Required", + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + if (errorMessage != null) ...[ const SizedBox(height: 4), Text( @@ -117,18 +130,34 @@ class _MultiFileUploadWidgetState extends State { ], const SizedBox(height: 8), - ...files.asMap().entries.map((entry) { + ...fileService.files.asMap().entries.map((entry) { final index = entry.key; - final file = entry.value; + final uploaded = entry.value; // UploadedFile - return ListTile( - dense: true, - contentPadding: EdgeInsets.zero, - title: Text(file.name, style: const TextStyle(fontSize: 14)), - trailing: IconButton( - icon: const Icon(Icons.close, color: Colors.red), - onPressed: () => _removeFile(index), - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + title: Text(uploaded.file.name, style: const TextStyle(fontSize: 14)), + trailing: IconButton( + icon: const Icon(Icons.close, color: Colors.red), + onPressed: () => _removeFile(index), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0, bottom: 8.0, right: 8.0), + child: TextField( + controller: uploaded.controller, + decoration: const InputDecoration( + labelText: 'Enter document name', + border: OutlineInputBorder(), + isDense: true, + ), + ), + ), + ], ); }), ], diff --git a/lib/pages/service/popup_helper.dart b/lib/pages/service/popup_helper.dart index f5067d5..7d86132 100644 --- a/lib/pages/service/popup_helper.dart +++ b/lib/pages/service/popup_helper.dart @@ -56,6 +56,19 @@ class PopupHelper { final double dialogHeight = isMobile ? MediaQuery.of(context).size.height * 0.17 : 150; + // Auto close after 3 seconds and redirect + Future.delayed(const Duration(seconds: 2), () { + if (Navigator.of(dialogContext).canPop()) { + Navigator.of(dialogContext).pop(); + } + + if (wellnessURL != null) { + launchURL(wellnessURL, context); + } else { + ToastHelper.showInfoToast(context, 'Invalid link or unavailable'); + } + }); + return Dialog( insetPadding: EdgeInsets.symmetric(horizontal: isMobile ? 40 : 0), shape: RoundedRectangleBorder( @@ -67,10 +80,12 @@ class PopupHelper { height: dialogHeight, padding: const EdgeInsets.all(20), child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( - 'You are being redirected out of this app. Click Proceed to Continue', + 'You are being redirected out of this app.', + // 'You are being redirected out of this app. Click Proceed to Continue', textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: isMobile ? 14 : 16, @@ -78,65 +93,65 @@ class PopupHelper { color: const Color(0xFF777777), ), ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: OutlinedButton( - onPressed: () => Navigator.pop(dialogContext), - style: OutlinedButton.styleFrom( - side: const BorderSide(color: Color(0xFFE26728)), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - padding: const EdgeInsets.symmetric(vertical: 12), - ), - child: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - 'Cancel', - style: GoogleFonts.poppins( - color: const Color(0xFFE26728), - fontSize: isMobile ? 14 : 16, - ), - ), - ), - ), - ), - const SizedBox(width: 15), - Expanded( - child: ElevatedButton( - onPressed: () { - Navigator.of(dialogContext).pop(); - if (wellnessURL != null) { - launchURL(wellnessURL, context); - } else { - ToastHelper.showInfoToast( - context, 'Invalid link or unavailable'); - } - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFE26728), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - padding: const EdgeInsets.symmetric(vertical: 12), - ), - child: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - 'Proceed', - style: GoogleFonts.poppins( - color: Colors.white, - fontSize: isMobile ? 14 : 16, - ), - ), - ), - ), - ), - ], - ), + // const SizedBox(height: 20), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // children: [ + // Expanded( + // child: OutlinedButton( + // onPressed: () => Navigator.pop(dialogContext), + // style: OutlinedButton.styleFrom( + // side: const BorderSide(color: Color(0xFFE26728)), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(10), + // ), + // padding: const EdgeInsets.symmetric(vertical: 12), + // ), + // child: FittedBox( + // fit: BoxFit.scaleDown, + // child: Text( + // 'Cancel', + // style: GoogleFonts.poppins( + // color: const Color(0xFFE26728), + // fontSize: isMobile ? 14 : 16, + // ), + // ), + // ), + // ), + // ), + // const SizedBox(width: 15), + // Expanded( + // child: ElevatedButton( + // onPressed: () { + // Navigator.of(dialogContext).pop(); + // if (wellnessURL != null) { + // launchURL(wellnessURL, context); + // } else { + // ToastHelper.showInfoToast( + // context, 'Invalid link or unavailable'); + // } + // }, + // style: ElevatedButton.styleFrom( + // backgroundColor: const Color(0xFFE26728), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(10), + // ), + // padding: const EdgeInsets.symmetric(vertical: 12), + // ), + // child: FittedBox( + // fit: BoxFit.scaleDown, + // child: Text( + // 'Proceed', + // style: GoogleFonts.poppins( + // color: Colors.white, + // fontSize: isMobile ? 14 : 16, + // ), + // ), + // ), + // ), + // ), + // ], + // ), ], ), ), @@ -206,4 +221,4 @@ class PopupHelper { }, ); } -} +} \ No newline at end of file