diff --git a/lib/customAppBar/top_app_bar.dart b/lib/customAppBar/top_app_bar.dart index e37f21a..49b0280 100644 --- a/lib/customAppBar/top_app_bar.dart +++ b/lib/customAppBar/top_app_bar.dart @@ -55,6 +55,54 @@ class _NhanceTopBarState extends State { return null; } + Map? _decodeBranchToken(String token) { + try { + final parts = token.split('.'); + if (parts.length != 3) return null; + + final payload = base64Url.normalize(parts[1]); + final decoded = utf8.decode(base64Url.decode(payload)); + final parsed = json.decode(decoded); + if (parsed is Map) return parsed; + if (parsed is Map) return Map.from(parsed); + return null; + } catch (_) { + return null; + } + } + + /// Read post/pre client + branch ids from selected branch JWT payload. + Map? _clientDetailsParamsFromBranch( + Map branch, + ) { + final token = branch['token']?.toString() ?? + tokenStorage.getCurrentToken() ?? + ''; + if (token.isEmpty) return null; + + final decoded = _decodeBranchToken(token); + if (decoded == null) return null; + + final postClientId = decoded['post_client_id']?.toString().trim() ?? ''; + final postBranchId = decoded['post_branch_id']?.toString().trim() ?? ''; + final preClientId = decoded['pre_client_id']?.toString().trim() ?? ''; + final preBranchId = decoded['pre_branch_id']?.toString().trim() ?? ''; + + if (postClientId.isEmpty && + postBranchId.isEmpty && + preClientId.isEmpty && + preBranchId.isEmpty) { + return null; + } + + return { + 'post_client_id': postClientId, + 'post_branch_id': postBranchId, + 'pre_client_id': preClientId, + 'pre_branch_id': preBranchId, + }; + } + Future _loadBranchLogos() async { final uniqueBranches = >{}; for (final branch in branches) { @@ -89,23 +137,11 @@ class _NhanceTopBarState extends State { ''; if (token.isEmpty) return null; - final clientId = branch['client_id']?.toString() ?? ''; - final branchId = branch['client_branch_id']?.toString() ?? ''; - final preBranchId = branch['pre_branch_id']?.toString() ?? branchId; - if (clientId.isEmpty || branchId.isEmpty) return null; + final params = _clientDetailsParamsFromBranch(branch); + if (params == null) return null; - final isPre = branch['enrollment_type']?.toString() == 'pre'; - final postClientId = isPre ? '' : clientId; - final postBranchId = isPre ? '' : branchId; - final preClientId = isPre ? clientId : clientId; - final preBranch = isPre ? branchId : preBranchId; - - final url = Uri.parse( - '${Environment.apiUrl}getClientDetails' - '?post_client_id=$postClientId' - '&post_branch_id=$postBranchId' - '&pre_client_id=$preClientId' - '&pre_branch_id=$preBranch', + final url = Uri.parse('${Environment.apiUrl}getClientDetails').replace( + queryParameters: params, ); final response = await http.get( diff --git a/lib/presentation/claims_overview/claims_overview_dashboard.dart b/lib/presentation/claims_overview/claims_overview_dashboard.dart index 4d12b3f..92d453a 100644 --- a/lib/presentation/claims_overview/claims_overview_dashboard.dart +++ b/lib/presentation/claims_overview/claims_overview_dashboard.dart @@ -141,15 +141,23 @@ class _ClaimsOverviewDashboardState extends State Future _logHrActivity(String activity) async { try { - final postId = await _tokenService.readValue('empHrId'); - final preId = await _tokenService.readValue('enrollmentEmpPrimaryId'); final token = await _tokenService.getCurrentToken(); + if (token == null || token.isEmpty) return; + + var postId = await _tokenService.readValue('empHrId'); + var preId = await _tokenService.readValue('enrollmentEmpPrimaryId'); if (postId == null || postId.isEmpty || preId == null || - preId.isEmpty || - token == null || - token.isEmpty) { + preId.isEmpty) { + final decoded = _tokenService.getDecodedToken(); + postId ??= decoded?['post_hr_id']?.toString(); + preId ??= decoded?['pre_hr_id']?.toString(); + } + if (postId == null || + postId.isEmpty || + preId == null || + preId.isEmpty) { return; } await _apiService.getPostLogHrActivity(postId, preId, token, activity); diff --git a/lib/presentation/email_template/reminder_email_template_dialog.dart b/lib/presentation/email_template/reminder_email_template_dialog.dart index acdf951..635374c 100644 --- a/lib/presentation/email_template/reminder_email_template_dialog.dart +++ b/lib/presentation/email_template/reminder_email_template_dialog.dart @@ -20,6 +20,7 @@ class ReminderEmailTemplateDialog extends StatefulWidget { final String defaultHtmlBody; final ReminderEmailTemplateMode mode; final Future Function(String subject, String htmlBody)? onSend; + final bool embedded; const ReminderEmailTemplateDialog({ super.key, @@ -32,6 +33,7 @@ class ReminderEmailTemplateDialog extends StatefulWidget { required this.defaultHtmlBody, this.mode = ReminderEmailTemplateMode.config, this.onSend, + this.embedded = false, }); @override @@ -460,10 +462,147 @@ class _ReminderEmailTemplateDialogState extends State Navigator.pop(context), + icon: const Icon(Icons.close, size: 20), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Subject', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(width: 12), + Expanded( + child: TextField( + controller: _subjectController, + style: GoogleFonts.poppins(fontSize: 14), + decoration: InputDecoration( + isDense: true, + filled: true, + fillColor: const Color(0xFFF5F5F5), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: BorderSide.none, + ), + ), + ), + ), + const SizedBox(width: 8), + _placeholdersMenu(), + const SizedBox(width: 8), + _webPointerGuard( + child: _tealButton( + label: 'Preview Mail', + onPressed: () => _showPreviewMail(), + width: 120, + ), + ), + ], + ), + ), + Expanded(child: _buildEditorArea()), + const Divider(height: 1), + Padding( + padding: const EdgeInsets.fromLTRB(20, 12, 20, 16), + child: Row( + children: [ + _webPointerGuard( + child: TextButton.icon( + onPressed: () => _toggleHtmlEditMode(), + icon: Icon( + Icons.code, + size: 16, + color: _isHtmlEditMode ? _teal : Colors.black54, + ), + label: Text( + _isHtmlEditMode ? 'Visual Editor' : 'Dev HTML', + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: _isHtmlEditMode ? _teal : Colors.black54, + ), + ), + ), + ), + const Spacer(), + _webPointerGuard( + child: _tealButton( + label: primaryLabel, + onPressed: _submit, + loading: _isSubmitting, + width: 120, + ), + ), + ], + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + if (widget.embedded) { + return _buildTemplateContent(); + } + final size = MediaQuery.sizeOf(context); return Dialog( @@ -473,132 +612,7 @@ class _ReminderEmailTemplateDialogState extends State Navigator.pop(context), - icon: const Icon(Icons.close, size: 20), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - 'Subject', - style: GoogleFonts.poppins( - fontSize: 13, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(width: 12), - Expanded( - child: TextField( - controller: _subjectController, - style: GoogleFonts.poppins(fontSize: 14), - decoration: InputDecoration( - isDense: true, - filled: true, - fillColor: const Color(0xFFF5F5F5), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 10, - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - borderSide: BorderSide.none, - ), - ), - ), - ), - const SizedBox(width: 8), - _placeholdersMenu(), - const SizedBox(width: 8), - _webPointerGuard( - child: _tealButton( - label: 'Preview Mail', - onPressed: () => _showPreviewMail(), - width: 120, - ), - ), - ], - ), - ), - Expanded(child: _buildEditorArea()), - const Divider(height: 1), - Padding( - padding: const EdgeInsets.fromLTRB(20, 12, 20, 16), - child: Row( - children: [ - _webPointerGuard( - child: TextButton.icon( - onPressed: () => _toggleHtmlEditMode(), - icon: Icon( - Icons.code, - size: 16, - color: _isHtmlEditMode ? _teal : Colors.black54, - ), - label: Text( - _isHtmlEditMode ? 'Visual Editor' : 'Dev HTML', - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: _isHtmlEditMode ? _teal : Colors.black54, - ), - ), - ), - ), - const Spacer(), - _webPointerGuard( - child: _tealButton( - label: primaryLabel, - onPressed: _submit, - loading: _isSubmitting, - width: 120, - ), - ), - ], - ), - ), - ], - ), + child: _buildTemplateContent(), ), ); } diff --git a/lib/presentation/hrPolicyDetails.dart b/lib/presentation/hrPolicyDetails.dart index 904071a..d769e29 100755 --- a/lib/presentation/hrPolicyDetails.dart +++ b/lib/presentation/hrPolicyDetails.dart @@ -1,3 +1,4 @@ +import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:nhancepolicy/customAppBar/customAppBar.dart'; @@ -216,6 +217,10 @@ class _HrPolicyDetailsState extends State localToken = widget.Token.isNotEmpty ? widget.Token : await tokenService.readValue('hr_Token'); + // Browser refresh often drops constructor Token — use session token. + if (localToken == null || localToken!.toString().trim().isEmpty) { + localToken = tokenService.getCurrentToken(); + } localTokenType = widget.TokenType.isNotEmpty ? widget.TokenType @@ -252,9 +257,65 @@ class _HrPolicyDetailsState extends State ? widget.is_ecard_bulk_download_for_employee : int.tryParse(savedBulk ?? '0') ?? 0; + // Persist so browser refresh can rebuild this page without empty API params. + await _persistPolicyContext(); + + if (!_hasRequiredPolicyContext()) { + logDebug( + 'hrPolicyDetails missing client/policy/branch after restore — ' + 'skipping API and returning to policies', + ); + if (!mounted) return; + setState(() { + _isLoadingCards = false; + isLoading = false; + }); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + Navigator.pushReplacementNamed(context, 'policies'); + }); + return; + } + getCDPoliciesDetails(); } + bool _hasRequiredPolicyContext() { + final clientId = (localClientId ?? '').toString().trim(); + final policyId = (localClientPolicyId ?? '').toString().trim(); + final branchId = (localClientBranchId ?? '').toString().trim(); + final token = (localToken ?? '').toString().trim(); + return clientId.isNotEmpty && + policyId.isNotEmpty && + branchId.isNotEmpty && + token.isNotEmpty; + } + + Future _persistPolicyContext() async { + Future writeIfPresent(String key, dynamic value) async { + final text = value?.toString().trim() ?? ''; + if (text.isEmpty) return; + await tokenService.writeValue(key, text); + } + + await writeIfPresent('hr_ClientId', localClientId); + await writeIfPresent('hr_policyTypeId', localPolicyTypeId); + await writeIfPresent('hr_ClientPoliyId', localClientPolicyId); + await writeIfPresent('hr_clientBranchId', localClientBranchId); + await writeIfPresent('hr_Token', localToken); + await writeIfPresent('hr_TokenType', localTokenType); + await writeIfPresent('hr_cardType', localCardType); + await writeIfPresent('hr_cardPolicyNo', localCardPolicyNo); + await writeIfPresent('hr_cardInsurer_name', localCardInsurerName); + await writeIfPresent('hr_cardPolicy_name', localCardPolicyName); + await writeIfPresent('hr_cardPolicy_ExpDate', localCardPolicyExpDate); + await writeIfPresent('hr_total_premium', localTotalPremium); + await tokenService.writeValue( + 'hr_is_ecard_bulk_download_for_employee', + localIsEcardBulkDownload.toString(), + ); + } + Future clearPolicyStorage() async { await tokenService.removeValue('hr_ClientId'); await tokenService.removeValue('hr_policyTypeId'); @@ -377,10 +438,28 @@ class _HrPolicyDetailsState extends State dynamic cardData, String? searchKey, }) async { - final clientId = localClientId ?? widget.ClientId; - final policyId = localClientPolicyId ?? widget.ClientPoliyId; - final branchId = localClientBranchId ?? widget.clientBranchId; - final token = localToken ?? widget.Token; + final clientId = (localClientId ?? widget.ClientId).toString().trim(); + final policyId = (localClientPolicyId ?? widget.ClientPoliyId).toString().trim(); + final branchId = + (localClientBranchId ?? widget.clientBranchId).toString().trim(); + var token = (localToken ?? widget.Token).toString().trim(); + if (token.isEmpty) { + token = tokenService.getCurrentToken()?.trim() ?? ''; + localToken = token; + } + + if (clientId.isEmpty || policyId.isEmpty || branchId.isEmpty || token.isEmpty) { + logDebug( + 'Skipping getEmployeeAndDependenceByClientId — empty params ' + 'client=$clientId policy=$policyId branch=$branchId tokenEmpty=${token.isEmpty}', + ); + return { + 'status': 'error', + 'code': 400, + 'message': 'Missing policy context', + 'data': [], + }; + } if (localTokenType == 'post') { return apiService.getEmployeeAndDependenceToApi( @@ -397,7 +476,7 @@ class _HrPolicyDetailsState extends State clientId, policyId, branchId, - token!, + token, cardData: cardData, searchKey: searchKey, ); @@ -1531,7 +1610,7 @@ class _HrPolicyDetailsState extends State } } - Future _openReminderConfigDialog() async { + Future _openReminderDialog() async { final clientId = localClientId ?? widget.ClientId; final clientPolicyId = localClientPolicyId ?? widget.ClientPoliyId; final clientBranchId = localClientBranchId ?? widget.clientBranchId; @@ -1541,7 +1620,7 @@ class _HrPolicyDetailsState extends State context: context, barrierDismissible: false, builder: (dialogContext) { - return _ReminderMailConfigDialog( + return _ReminderHubDialog( clientId: clientId, clientPolicyId: clientPolicyId, clientBranchId: clientBranchId, @@ -1549,6 +1628,10 @@ class _HrPolicyDetailsState extends State apiService: apiService, defaultSubject: _defaultReminderSubject(), defaultHtmlBody: _defaultReminderHtmlBody(), + onSend: (subject, htmlBody) => _sendEnrollmentReminder( + emailSubject: subject, + emailBody: htmlBody, + ), ); }, ); @@ -1563,34 +1646,6 @@ class _HrPolicyDetailsState extends State return kDefaultEnrollmentReminderHtmlBody; } - Future _openReminderTemplateDialog() async { - final clientId = localClientId ?? widget.ClientId; - final clientPolicyId = localClientPolicyId ?? widget.ClientPoliyId; - final clientBranchId = localClientBranchId ?? widget.clientBranchId; - final token = localToken ?? widget.Token; - - await showDialog( - context: context, - barrierDismissible: false, - builder: (dialogContext) { - return ReminderEmailTemplateDialog( - clientId: clientId, - clientPolicyId: clientPolicyId, - clientBranchId: clientBranchId, - token: token, - apiService: apiService, - defaultSubject: _defaultReminderSubject(), - defaultHtmlBody: _defaultReminderHtmlBody(), - mode: ReminderEmailTemplateMode.send, - onSend: (subject, htmlBody) => _sendEnrollmentReminder( - emailSubject: subject, - emailBody: htmlBody, - ), - ); - }, - ); - } - Future _sendEnrollmentReminder({ required String emailSubject, required String emailBody, @@ -1920,15 +1975,9 @@ class _HrPolicyDetailsState extends State if (localTokenType == 'pre') ...[ ( label: 'Reminder', - icon: Icons.tune_rounded, + icon: Icons.notifications_active_outlined, color: const Color(0xFF009195), - onTap: _openReminderConfigDialog, - ), - ( - label: 'Mail Template', - icon: Icons.mail_outline_rounded, - color: const Color(0xFFE26728), - onTap: _isSendingReminder ? null : _openReminderTemplateDialog, + onTap: _isSendingReminder ? null : _openReminderDialog, ), ], if (localTokenType == 'post') ...[ @@ -2364,18 +2413,12 @@ class _HrPolicyDetailsState extends State ), if (localTokenType == 'pre') ...[ _buildHeaderActionButton( - label: 'Reminder Config', + label: 'Reminder', color: const Color(0xFF009195), - width: isCompact ? 130 : 150, - onPressed: _openReminderConfigDialog, - ), - _buildHeaderActionButton( - label: 'Reminder Mail Template', - color: const Color(0xFFE26728), - width: isCompact ? 170 : 210, + width: isCompact ? 120 : 130, onPressed: _isSendingReminder ? null - : _openReminderTemplateDialog, + : _openReminderDialog, isLoading: _isSendingReminder, ), ], @@ -3193,7 +3236,9 @@ class _HrPolicyDetailsState extends State } Widget _buildCDDataTable(BuildContext context) { - final showUHID = localPolicyTypeId != '6' && localPolicyTypeId != '7'; + final showUHID = localTokenType == 'post' && + localPolicyTypeId != '6' && + localPolicyTypeId != '7'; final showLoggedIn = localTokenType == 'pre'; final showAction = localTokenType != 'pre' && (hasAnyEcardLink || hasModule); @@ -3205,7 +3250,7 @@ class _HrPolicyDetailsState extends State pinned: true, delegate: _CDHeaderDelegate( showUHID: showUHID, - uhidHeaderLabel: localTokenType == 'post' ? 'TPA ID' : 'UHID', + uhidHeaderLabel: 'TPA ID', showLoggedIn: showLoggedIn, showAction: showAction, ), @@ -3256,14 +3301,14 @@ class _HrPolicyDetailsState extends State ), ), - /// UHID / TPA ID - if (localPolicyTypeId != '6' && localPolicyTypeId != '7') + /// TPA ID (post only) + if (localTokenType == 'post' && + localPolicyTypeId != '6' && + localPolicyTypeId != '7') Expanded( flex: 2, child: Text( - localTokenType == 'post' - ? (item['tpa_id'] ?? '-') - : (item['uhid'] ?? '-'), + item['tpa_id'] ?? '-', style: _dataBold, ), ), @@ -4500,6 +4545,150 @@ class _PolicyTermsDialog extends StatelessWidget { } } +class _ReminderHubDialog extends StatefulWidget { + final String clientId; + final String clientPolicyId; + final String clientBranchId; + final String token; + final ApiService apiService; + final String defaultSubject; + final String defaultHtmlBody; + final Future Function(String subject, String htmlBody) onSend; + + const _ReminderHubDialog({ + required this.clientId, + required this.clientPolicyId, + required this.clientBranchId, + required this.token, + required this.apiService, + required this.defaultSubject, + required this.defaultHtmlBody, + required this.onSend, + }); + + @override + State<_ReminderHubDialog> createState() => _ReminderHubDialogState(); +} + +class _ReminderHubDialogState extends State<_ReminderHubDialog> + with SingleTickerProviderStateMixin { + static const _teal = Color(0xFF009195); + late final TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + _tabController.addListener(_onTabChanged); + } + + void _onTabChanged() { + if (_tabController.indexIsChanging) return; + setState(() {}); + } + + @override + void dispose() { + _tabController.removeListener(_onTabChanged); + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final size = MediaQuery.sizeOf(context); + final isConfigTab = _tabController.index == 0; + final dialogWidth = isConfigTab + ? math.min(520.0, size.width - 32) + : size.width * 0.95; + final dialogHeight = isConfigTab + ? math.min(460.0, size.height * 0.72) + : size.height * 0.92; + + return Dialog( + insetPadding: const EdgeInsets.all(16), + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: SizedBox( + width: dialogWidth, + height: dialogHeight, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(20, 16, 8, 0), + child: Row( + children: [ + Text( + 'Enrollment Reminder', + style: GoogleFonts.poppins( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const Spacer(), + IconButton( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.close, size: 20), + ), + ], + ), + ), + TabBar( + controller: _tabController, + labelColor: _teal, + unselectedLabelColor: Colors.black54, + indicatorColor: _teal, + labelStyle: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w500, + ), + tabs: const [ + Tab(text: 'Setup'), + Tab(text: 'Mail Template'), + ], + ), + const Divider(height: 1), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + _ReminderMailConfigDialog( + clientId: widget.clientId, + clientPolicyId: widget.clientPolicyId, + clientBranchId: widget.clientBranchId, + token: widget.token, + apiService: widget.apiService, + defaultSubject: widget.defaultSubject, + defaultHtmlBody: widget.defaultHtmlBody, + embedded: true, + ), + ReminderEmailTemplateDialog( + clientId: widget.clientId, + clientPolicyId: widget.clientPolicyId, + clientBranchId: widget.clientBranchId, + token: widget.token, + apiService: widget.apiService, + defaultSubject: widget.defaultSubject, + defaultHtmlBody: widget.defaultHtmlBody, + mode: ReminderEmailTemplateMode.send, + onSend: widget.onSend, + embedded: true, + ), + ], + ), + ), + ], + ), + ), + ); + } +} + class _ReminderMailConfigDialog extends StatefulWidget { final String clientId; final String clientPolicyId; @@ -4508,6 +4697,7 @@ class _ReminderMailConfigDialog extends StatefulWidget { final ApiService apiService; final String defaultSubject; final String defaultHtmlBody; + final bool embedded; const _ReminderMailConfigDialog({ required this.clientId, @@ -4517,6 +4707,7 @@ class _ReminderMailConfigDialog extends StatefulWidget { required this.apiService, required this.defaultSubject, required this.defaultHtmlBody, + this.embedded = false, }); @override @@ -4854,9 +5045,11 @@ class _ReminderMailConfigDialogState extends State<_ReminderMailConfigDialog> { TextField( controller: _reminderDaysController, onChanged: (value) => _reminderDaysText = value, - style: GoogleFonts.poppins(fontSize: 14), + style: GoogleFonts.poppins(fontSize: 13), decoration: InputDecoration( + isDense: true, hintText: helperText, + hintStyle: GoogleFonts.poppins(fontSize: 12, color: Colors.black38), filled: true, fillColor: const Color(0xFFF5F5F5), border: OutlineInputBorder( @@ -4897,9 +5090,188 @@ class _ReminderMailConfigDialogState extends State<_ReminderMailConfigDialog> { ); } + Widget _buildConfigContent() { + final isBusy = _isSaving; + final formMaxWidth = widget.embedded ? 420.0 : 560.0; + + if (_isLoadingConfig) { + return const Center(child: CircularProgressIndicator()); + } + + final form = ConstrainedBox( + constraints: BoxConstraints(maxWidth: formMaxWidth), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + 'Configure the schedule for automated enrollment reminder emails.', + style: GoogleFonts.poppins( + fontSize: 13, + color: Colors.black54, + ), + ), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFFF8FAFA), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0xFFE8EEF0)), + ), + child: Row( + children: [ + Expanded( + child: Text( + 'Enable scheduled reminders', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ), + Switch( + value: _isEnabled, + activeColor: const Color(0xFF009195), + onChanged: isBusy + ? null + : (value) => setState(() => _isEnabled = value), + ), + ], + ), + ), + const SizedBox(height: 16), + Text( + 'Frequency', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 6), + SizedBox( + width: double.infinity, + child: DropdownButtonFormField( + value: _frequency, + isExpanded: true, + isDense: true, + decoration: InputDecoration( + filled: true, + fillColor: const Color(0xFFF5F5F5), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + ), + items: _frequencies.entries + .map( + (entry) => DropdownMenuItem( + value: entry.key, + child: Text( + entry.value, + style: GoogleFonts.poppins(fontSize: 13), + ), + ), + ) + .toList(), + onChanged: isBusy + ? null + : (value) { + if (value == null) return; + setState(() { + _frequency = value; + _reminderDaysText = ''; + _reminderDaysController.clear(); + _selectedWorkingDays.clear(); + }); + }, + ), + ), + const SizedBox(height: 14), + _buildDaysField(), + ], + ), + ); + + return SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(20, 16, 20, 8), + child: Align( + alignment: Alignment.topCenter, + child: form, + ), + ); + } + + Widget _buildConfigActions() { + final isBusy = _isSaving; + final formMaxWidth = widget.embedded ? 420.0 : 560.0; + + return Padding( + padding: const EdgeInsets.fromLTRB(20, 8, 20, 16), + child: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: formMaxWidth), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: isBusy ? null : () => Navigator.pop(context), + child: Text( + 'Cancel', + style: GoogleFonts.poppins(color: Colors.black54), + ), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: isBusy || _isLoadingConfig ? null : _saveConfig, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF009195), + minimumSize: const Size(88, 36), + padding: const EdgeInsets.symmetric(horizontal: 16), + ), + child: _isSaving + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : Text( + 'Save', + style: GoogleFonts.poppins(color: Colors.white), + ), + ), + ], + ), + ), + ), + ); + } + @override Widget build(BuildContext context) { - final isBusy = _isSaving; + if (widget.embedded) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Align( + alignment: Alignment.topCenter, + child: _buildConfigContent(), + ), + ), + const Divider(height: 1), + _buildConfigActions(), + ], + ); + } return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), @@ -4909,119 +5281,15 @@ class _ReminderMailConfigDialogState extends State<_ReminderMailConfigDialog> { ), content: SizedBox( width: 560, - child: _isLoadingConfig - ? const SizedBox( - height: 180, - child: Center(child: CircularProgressIndicator()), - ) - : SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Configure the schedule for automated enrollment reminder emails.', - style: GoogleFonts.poppins( - fontSize: 13, - color: Colors.black54, - ), - ), - const SizedBox(height: 16), - SwitchListTile( - contentPadding: EdgeInsets.zero, - title: Text( - 'Enable scheduled reminders', - style: GoogleFonts.poppins( - fontSize: 13, - fontWeight: FontWeight.w600, - ), - ), - value: _isEnabled, - activeColor: const Color(0xFF009195), - onChanged: isBusy - ? null - : (value) => setState(() => _isEnabled = value), - ), - const SizedBox(height: 8), - Text( - 'Frequency', - style: GoogleFonts.poppins( - fontSize: 13, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 6), - DropdownButtonFormField( - value: _frequency, - isExpanded: true, - decoration: InputDecoration( - filled: true, - fillColor: const Color(0xFFF5F5F5), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 10, - ), - ), - items: _frequencies.entries - .map( - (entry) => DropdownMenuItem( - value: entry.key, - child: Text( - entry.value, - style: GoogleFonts.poppins(fontSize: 14), - ), - ), - ) - .toList(), - onChanged: isBusy - ? null - : (value) { - if (value == null) return; - setState(() { - _frequency = value; - _reminderDaysText = ''; - _reminderDaysController.clear(); - _selectedWorkingDays.clear(); - }); - }, - ), - const SizedBox(height: 14), - _buildDaysField(), - const SizedBox(height: 20), - // OutlinedButton.icon( - // onPressed: isBusy ? null : _openTemplateEditor, - // icon: const Icon(Icons.edit_outlined, size: 18), - // label: Text( - // 'Edit Email Template', - // style: GoogleFonts.poppins( - // fontSize: 13, - // fontWeight: FontWeight.w600, - // ), - // ), - // style: OutlinedButton.styleFrom( - // foregroundColor: const Color(0xFF009195), - // side: const BorderSide(color: Color(0xFF009195)), - // padding: const EdgeInsets.symmetric( - // horizontal: 16, - // vertical: 12, - // ), - // ), - // ), - ], - ), - ), + child: _buildConfigContent(), ), actions: [ TextButton( - onPressed: isBusy ? null : () => Navigator.pop(context), + onPressed: _isSaving ? null : () => Navigator.pop(context), child: Text('Cancel', style: GoogleFonts.poppins(color: Colors.black54)), ), ElevatedButton( - onPressed: isBusy || _isLoadingConfig ? null : _saveConfig, + onPressed: _isSaving || _isLoadingConfig ? null : _saveConfig, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF009195), ), diff --git a/lib/presentation/policies.dart b/lib/presentation/policies.dart index 687efd1..ac77348 100644 --- a/lib/presentation/policies.dart +++ b/lib/presentation/policies.dart @@ -585,6 +585,46 @@ class _PolicyGrid extends StatelessWidget { return allocg == 'non-eb'; } + /// Persist policy context so hrPolicyDetails survives browser refresh. + Future _persistHrPolicyContext( + TokenStorageService tokenService, { + required String clientId, + required String policyTypeId, + required String clientPolicyId, + required String clientBranchId, + required String token, + required String tokenType, + required String cardType, + required String cardPolicyNo, + required String cardInsurerName, + required String cardPolicyName, + required String cardPolicyExpDate, + required String totalPremium, + required dynamic isEcardBulkDownload, + }) async { + Future write(String key, dynamic value) async { + final text = value?.toString() ?? ''; + await tokenService.writeValue(key, text); + } + + await write('hr_ClientId', clientId); + await write('hr_policyTypeId', policyTypeId); + await write('hr_ClientPoliyId', clientPolicyId); + await write('hr_clientBranchId', clientBranchId); + await write('hr_Token', token); + await write('hr_TokenType', tokenType); + await write('hr_cardType', cardType); + await write('hr_cardPolicyNo', cardPolicyNo); + await write('hr_cardInsurer_name', cardInsurerName); + await write('hr_cardPolicy_name', cardPolicyName); + await write('hr_cardPolicy_ExpDate', cardPolicyExpDate); + await write('hr_total_premium', totalPremium); + await write( + 'hr_is_ecard_bulk_download_for_employee', + isEcardBulkDownload ?? 0, + ); + } + ResponsiveGridConfig _getGridConfig( BuildContext context, bool isEnrollment, @@ -728,6 +768,23 @@ class _PolicyGrid extends StatelessWidget { return; } + await _persistHrPolicyContext( + tokenService, + clientId: clientId, + policyTypeId: data['policy_type_id'].toString(), + clientPolicyId: data['client_policy_id'].toString(), + clientBranchId: branchId, + token: token, + tokenType: 'pre', + cardType: data['type'].toString(), + cardPolicyNo: data['policy_no'].toString(), + cardInsurerName: data['insurer_short_name'].toString(), + cardPolicyName: data['policy_name'].toString(), + cardPolicyExpDate: data['policy_expiry_date'].toString(), + totalPremium: '', + isEcardBulkDownload: 0, + ); + Navigator.push( context, MaterialPageRoute( @@ -801,6 +858,24 @@ class _PolicyGrid extends StatelessWidget { return; } + await _persistHrPolicyContext( + tokenService, + clientId: clientId, + policyTypeId: data['policy_type_id'].toString(), + clientPolicyId: data['client_policy_id'].toString(), + clientBranchId: branchId, + token: token, + tokenType: 'post', + cardType: data['type'].toString(), + cardPolicyNo: data['policy_no'].toString(), + cardInsurerName: data['insurer_short_name'].toString(), + cardPolicyName: data['policy_name'].toString(), + cardPolicyExpDate: data['policy_expiry_date'].toString(), + totalPremium: data['total_premium'].toString(), + isEcardBulkDownload: + data['is_ecard_bulk_download_for_employee'], + ); + Navigator.push( context, MaterialPageRoute( diff --git a/lib/service/api_service.dart b/lib/service/api_service.dart index b242e4e..7858021 100755 --- a/lib/service/api_service.dart +++ b/lib/service/api_service.dart @@ -595,6 +595,7 @@ class ApiService { final headers = { 'Authorization': 'Bearer ${token ?? ''}', + 'Content-Type': 'application/json', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', };