reminder and config and ecard
This commit is contained in:
parent
3abde687ca
commit
2120e997f0
@ -91,10 +91,10 @@ class _ClaimsLiveIndicatorState extends State<ClaimsLiveIndicator>
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
'Last Updated',
|
'MIS Creation Date',
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w400,
|
||||||
color: ClaimsOverviewTheme.green,
|
color: ClaimsOverviewTheme.green,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -102,7 +102,8 @@ class _ClaimsLiveIndicatorState extends State<ClaimsLiveIndicator>
|
|||||||
Text(
|
Text(
|
||||||
'· $createdAt',
|
'· $createdAt',
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 10,
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
color: ClaimsOverviewTheme.green,
|
color: ClaimsOverviewTheme.green,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -30,6 +30,8 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
|||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
late AnimationController _headerFadeController;
|
late AnimationController _headerFadeController;
|
||||||
late Animation<double> _headerFade;
|
late Animation<double> _headerFade;
|
||||||
|
late final ScrollController _scrollController;
|
||||||
|
late final List<GlobalKey> _sectionKeys;
|
||||||
|
|
||||||
late final ApiService _apiService;
|
late final ApiService _apiService;
|
||||||
final _tokenService = TokenStorageService();
|
final _tokenService = TokenStorageService();
|
||||||
@ -63,6 +65,8 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_apiService = ApiService(context);
|
_apiService = ApiService(context);
|
||||||
|
_scrollController = ScrollController();
|
||||||
|
_sectionKeys = List.generate(_tabs.length, (_) => GlobalKey());
|
||||||
_tabController = TabController(length: _tabs.length, vsync: this);
|
_tabController = TabController(length: _tabs.length, vsync: this);
|
||||||
_tabController.addListener(_onTabChanged);
|
_tabController.addListener(_onTabChanged);
|
||||||
|
|
||||||
@ -87,6 +91,20 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _scrollToSection(int index) {
|
||||||
|
setState(() => _currentTab = index);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final sectionContext = _sectionKeys[index].currentContext;
|
||||||
|
if (sectionContext == null) return;
|
||||||
|
Scrollable.ensureVisible(
|
||||||
|
sectionContext,
|
||||||
|
duration: const Duration(milliseconds: 450),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
alignment: 0.02,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _loadDashboard({bool reloadPolicies = false}) async {
|
Future<void> _loadDashboard({bool reloadPolicies = false}) async {
|
||||||
final isInitialLoad = _replayToken == 0;
|
final isInitialLoad = _replayToken == 0;
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -407,6 +425,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_tabController.removeListener(_onTabChanged);
|
_tabController.removeListener(_onTabChanged);
|
||||||
_tabController.dispose();
|
_tabController.dispose();
|
||||||
|
_scrollController.dispose();
|
||||||
_headerFadeController.dispose();
|
_headerFadeController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -433,23 +452,30 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
|||||||
child: EnrollmentOverviewScope(
|
child: EnrollmentOverviewScope(
|
||||||
key: ValueKey('enrollment-$_selectedPolicyId-$_replayToken'),
|
key: ValueKey('enrollment-$_selectedPolicyId-$_replayToken'),
|
||||||
data: _enrollmentViewData,
|
data: _enrollmentViewData,
|
||||||
child: TabBarView(
|
|
||||||
controller: _tabController,
|
|
||||||
physics: _isRefreshing
|
|
||||||
? const NeverScrollableScrollPhysics()
|
|
||||||
: null,
|
|
||||||
children: List.generate(
|
|
||||||
_tabs.length,
|
|
||||||
(i) => Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 16),
|
|
||||||
child: ClaimsTabAnimatedShell(
|
child: ClaimsTabAnimatedShell(
|
||||||
isLoading: _isLoading,
|
isLoading: _isLoading,
|
||||||
skeletonBlocks: i == 0 ? 5 : 4,
|
skeletonBlocks: 5,
|
||||||
child: ClaimsOverviewTab(
|
child: Scrollbar(
|
||||||
|
controller: _scrollController,
|
||||||
|
thumbVisibility: true,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
|
physics: _isRefreshing
|
||||||
|
? const NeverScrollableScrollPhysics()
|
||||||
|
: const ClampingScrollPhysics(),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
for (var i = 0; i < _tabs.length; i++) ...[
|
||||||
|
_buildSection(
|
||||||
index: i,
|
index: i,
|
||||||
replayToken: _replayToken,
|
replayToken: _replayToken,
|
||||||
isActive: _currentTab == i,
|
|
||||||
),
|
),
|
||||||
|
if (i < _tabs.length - 1)
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -697,6 +723,7 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
|||||||
),
|
),
|
||||||
TabBar(
|
TabBar(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
|
onTap: _scrollToSection,
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
tabAlignment: TabAlignment.start,
|
tabAlignment: TabAlignment.start,
|
||||||
labelColor: ClaimsOverviewTheme.primary,
|
labelColor: ClaimsOverviewTheme.primary,
|
||||||
@ -745,6 +772,56 @@ class _ClaimsOverviewDashboardState extends State<ClaimsOverviewDashboard>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildSection({
|
||||||
|
required int index,
|
||||||
|
required int replayToken,
|
||||||
|
}) {
|
||||||
|
final tab = _tabs[index];
|
||||||
|
return Container(
|
||||||
|
key: _sectionKeys[index],
|
||||||
|
margin: const EdgeInsets.only(top: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ClaimsOverviewTheme.cardBg,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: ClaimsOverviewTheme.border),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
tab.$1,
|
||||||
|
size: 22,
|
||||||
|
color: ClaimsOverviewTheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
tab.$2,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: ClaimsOverviewTheme.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
ClaimsOverviewTab(
|
||||||
|
index: index,
|
||||||
|
replayToken: replayToken,
|
||||||
|
isActive: true,
|
||||||
|
scrollable: false,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildFooter() {
|
Widget _buildFooter() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
|||||||
@ -35,7 +35,7 @@ const _tabNames = [
|
|||||||
const _tabFallbackHeights = [
|
const _tabFallbackHeights = [
|
||||||
720.0,
|
720.0,
|
||||||
680.0,
|
680.0,
|
||||||
660.0,
|
820.0,
|
||||||
760.0,
|
760.0,
|
||||||
680.0,
|
680.0,
|
||||||
580.0,
|
580.0,
|
||||||
@ -255,8 +255,10 @@ Widget _buildTabCapture({
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _waitForPaint() async {
|
Future<void> _waitForPaint() async {
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
await WidgetsBinding.instance.endOfFrame;
|
await WidgetsBinding.instance.endOfFrame;
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 32));
|
await Future<void>.delayed(const Duration(milliseconds: 48));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double _capturePixelRatio({required bool isEnrollmentTab}) {
|
double _capturePixelRatio({required bool isEnrollmentTab}) {
|
||||||
@ -285,6 +287,7 @@ Future<Uint8List?> _captureTabPng({
|
|||||||
required OverlayState overlay,
|
required OverlayState overlay,
|
||||||
required Widget child,
|
required Widget child,
|
||||||
required double fallbackHeight,
|
required double fallbackHeight,
|
||||||
|
double? maxMeasureHeight,
|
||||||
bool isEnrollmentTab = false,
|
bool isEnrollmentTab = false,
|
||||||
ClaimsPdfExportProgress? onProgress,
|
ClaimsPdfExportProgress? onProgress,
|
||||||
String? progressLabel,
|
String? progressLabel,
|
||||||
@ -309,14 +312,19 @@ Future<Uint8List?> _captureTabPng({
|
|||||||
key: boundaryKey,
|
key: boundaryKey,
|
||||||
child: MediaQuery(
|
child: MediaQuery(
|
||||||
data: const MediaQueryData(),
|
data: const MediaQueryData(),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: _captureWidth,
|
maxWidth: _captureWidth,
|
||||||
maxHeight: isEnrollmentTab
|
minHeight: fallbackHeight,
|
||||||
|
maxHeight: maxMeasureHeight ??
|
||||||
|
(isEnrollmentTab
|
||||||
? _enrollmentMaxMeasureHeight
|
? _enrollmentMaxMeasureHeight
|
||||||
: _maxMeasureHeight,
|
: _maxMeasureHeight),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
),
|
),
|
||||||
child: ClipRect(child: child),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -339,6 +347,15 @@ Future<Uint8List?> _captureTabPng({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final measuredHeight = renderObject.size.height;
|
||||||
|
if (measuredHeight < fallbackHeight * 0.85) {
|
||||||
|
debugPrint(
|
||||||
|
'PDF export: $progressLabel measured ${measuredHeight.toStringAsFixed(0)}px '
|
||||||
|
'(expected >= ${fallbackHeight.toStringAsFixed(0)}px)',
|
||||||
|
);
|
||||||
|
await _waitForPaint();
|
||||||
|
}
|
||||||
|
|
||||||
onProgress?.call(0, 0, '$progressLabel (rasterize…)');
|
onProgress?.call(0, 0, '$progressLabel (rasterize…)');
|
||||||
|
|
||||||
var ratio = _capturePixelRatio(isEnrollmentTab: isEnrollmentTab);
|
var ratio = _capturePixelRatio(isEnrollmentTab: isEnrollmentTab);
|
||||||
@ -455,6 +472,7 @@ Future<void> exportClaimsOverviewChartsPdf({
|
|||||||
overlay: overlay,
|
overlay: overlay,
|
||||||
child: tabWidget,
|
child: tabWidget,
|
||||||
fallbackHeight: _tabFallbackHeights[i],
|
fallbackHeight: _tabFallbackHeights[i],
|
||||||
|
maxMeasureHeight: i == 2 ? 1000.0 : null,
|
||||||
isEnrollmentTab: i == 6,
|
isEnrollmentTab: i == 6,
|
||||||
onProgress: onProgress,
|
onProgress: onProgress,
|
||||||
progressLabel: captureLabel,
|
progressLabel: captureLabel,
|
||||||
|
|||||||
@ -12,8 +12,15 @@ import 'claims_overview_widgets.dart';
|
|||||||
Widget _tabBodyWrapper({
|
Widget _tabBodyWrapper({
|
||||||
required bool forPdfExport,
|
required bool forPdfExport,
|
||||||
required Widget child,
|
required Widget child,
|
||||||
|
bool scrollable = true,
|
||||||
}) {
|
}) {
|
||||||
if (forPdfExport) return child;
|
if (forPdfExport) return child;
|
||||||
|
if (!scrollable) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 24),
|
padding: const EdgeInsets.fromLTRB(20, 0, 20, 24),
|
||||||
child: child,
|
child: child,
|
||||||
@ -38,6 +45,7 @@ class ClaimsOverviewTab extends StatelessWidget {
|
|||||||
final int replayToken;
|
final int replayToken;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final bool forPdfExport;
|
final bool forPdfExport;
|
||||||
|
final bool scrollable;
|
||||||
|
|
||||||
const ClaimsOverviewTab({
|
const ClaimsOverviewTab({
|
||||||
super.key,
|
super.key,
|
||||||
@ -45,6 +53,7 @@ class ClaimsOverviewTab extends StatelessWidget {
|
|||||||
required this.replayToken,
|
required this.replayToken,
|
||||||
this.isActive = false,
|
this.isActive = false,
|
||||||
this.forPdfExport = false,
|
this.forPdfExport = false,
|
||||||
|
this.scrollable = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -53,33 +62,40 @@ class ClaimsOverviewTab extends StatelessWidget {
|
|||||||
0 => _OverviewTab(
|
0 => _OverviewTab(
|
||||||
replayToken: replayToken,
|
replayToken: replayToken,
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
),
|
),
|
||||||
1 => _PolicyExperienceTab(
|
1 => _PolicyExperienceTab(
|
||||||
replayToken: replayToken,
|
replayToken: replayToken,
|
||||||
isActive: isActive || forPdfExport,
|
isActive: isActive || forPdfExport,
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
),
|
),
|
||||||
2 => _ClaimsAnalysisTab(
|
2 => _ClaimsAnalysisTab(
|
||||||
replayToken: replayToken,
|
replayToken: replayToken,
|
||||||
isActive: isActive || forPdfExport,
|
isActive: isActive || forPdfExport,
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
),
|
),
|
||||||
3 => _DemographicsTab(
|
3 => _DemographicsTab(
|
||||||
replayToken: replayToken,
|
replayToken: replayToken,
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
),
|
),
|
||||||
4 => _HospitalsTab(
|
4 => _HospitalsTab(
|
||||||
replayToken: replayToken,
|
replayToken: replayToken,
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
),
|
),
|
||||||
5 => _SpecialtyTab(
|
5 => _SpecialtyTab(
|
||||||
replayToken: replayToken,
|
replayToken: replayToken,
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
),
|
),
|
||||||
6 => _EnrollmentTab(
|
6 => _EnrollmentTab(
|
||||||
replayToken: replayToken,
|
replayToken: replayToken,
|
||||||
isActive: isActive || forPdfExport,
|
isActive: isActive || forPdfExport,
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
),
|
),
|
||||||
_ => const SizedBox(),
|
_ => const SizedBox(),
|
||||||
};
|
};
|
||||||
@ -89,10 +105,12 @@ class ClaimsOverviewTab extends StatelessWidget {
|
|||||||
class _OverviewTab extends StatelessWidget {
|
class _OverviewTab extends StatelessWidget {
|
||||||
final int replayToken;
|
final int replayToken;
|
||||||
final bool forPdfExport;
|
final bool forPdfExport;
|
||||||
|
final bool scrollable;
|
||||||
|
|
||||||
const _OverviewTab({
|
const _OverviewTab({
|
||||||
required this.replayToken,
|
required this.replayToken,
|
||||||
this.forPdfExport = false,
|
this.forPdfExport = false,
|
||||||
|
this.scrollable = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -100,6 +118,7 @@ class _OverviewTab extends StatelessWidget {
|
|||||||
final d = ClaimsOverviewScope.of(context);
|
final d = ClaimsOverviewScope.of(context);
|
||||||
return _tabBodyWrapper(
|
return _tabBodyWrapper(
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -134,6 +153,7 @@ class _OverviewTab extends StatelessWidget {
|
|||||||
left: ClaimsSectionCard(
|
left: ClaimsSectionCard(
|
||||||
title: 'Claims Experience',
|
title: 'Claims Experience',
|
||||||
icon: Icons.analytics_outlined,
|
icon: Icons.analytics_outlined,
|
||||||
|
minHeight: ClaimsSectionCard.pairedSectionMinHeight,
|
||||||
child: ClaimsExperienceBody(
|
child: ClaimsExperienceBody(
|
||||||
incurredClaims: d.incurredClaims,
|
incurredClaims: d.incurredClaims,
|
||||||
incurredRatio: d.incurredRatio,
|
incurredRatio: d.incurredRatio,
|
||||||
@ -145,6 +165,7 @@ class _OverviewTab extends StatelessWidget {
|
|||||||
right: ClaimsSectionCard(
|
right: ClaimsSectionCard(
|
||||||
title: 'Inception',
|
title: 'Inception',
|
||||||
icon: Icons.flag_outlined,
|
icon: Icons.flag_outlined,
|
||||||
|
minHeight: ClaimsSectionCard.pairedSectionMinHeight,
|
||||||
child: ClaimsInceptionBody(
|
child: ClaimsInceptionBody(
|
||||||
inceptionEmployee: d.inceptionEmployee,
|
inceptionEmployee: d.inceptionEmployee,
|
||||||
inceptionLives: d.inceptionLives,
|
inceptionLives: d.inceptionLives,
|
||||||
@ -160,8 +181,12 @@ class _OverviewTab extends StatelessWidget {
|
|||||||
required int baseIndex,
|
required int baseIndex,
|
||||||
required Widget left,
|
required Widget left,
|
||||||
required Widget right,
|
required Widget right,
|
||||||
|
bool equalizeHeight = false,
|
||||||
}) {
|
}) {
|
||||||
return ClaimsTabPanelRow(
|
final row = ClaimsTabPanelRow(
|
||||||
|
crossAxisAlignment: equalizeHeight
|
||||||
|
? CrossAxisAlignment.stretch
|
||||||
|
: CrossAxisAlignment.start,
|
||||||
panels: [
|
panels: [
|
||||||
ClaimsTabPanel(
|
ClaimsTabPanel(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@ -181,6 +206,8 @@ class _OverviewTab extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
if (!equalizeHeight) return row;
|
||||||
|
return IntrinsicHeight(child: row);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -189,11 +216,13 @@ class _PolicyExperienceTab extends StatefulWidget {
|
|||||||
final int replayToken;
|
final int replayToken;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final bool forPdfExport;
|
final bool forPdfExport;
|
||||||
|
final bool scrollable;
|
||||||
|
|
||||||
const _PolicyExperienceTab({
|
const _PolicyExperienceTab({
|
||||||
required this.replayToken,
|
required this.replayToken,
|
||||||
required this.isActive,
|
required this.isActive,
|
||||||
this.forPdfExport = false,
|
this.forPdfExport = false,
|
||||||
|
this.scrollable = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -240,41 +269,33 @@ class _PolicyExperienceTabState extends State<_PolicyExperienceTab>
|
|||||||
final countSeries = d.claimsCountByStatus;
|
final countSeries = d.claimsCountByStatus;
|
||||||
|
|
||||||
final kpiMetrics = [
|
final kpiMetrics = [
|
||||||
('Total Claims', d.sidebarTotalClaims, Icons.tag, ClaimsOverviewTheme.blue),
|
('Total Claims', d.sidebarTotalClaims, null, Icons.tag, ClaimsOverviewTheme.blue),
|
||||||
(
|
(
|
||||||
'Total Incurred Amount',
|
'Total Incurred Amount',
|
||||||
d.sidebarIncurredAmount,
|
d.sidebarIncurredAmount,
|
||||||
|
null,
|
||||||
Icons.currency_rupee,
|
Icons.currency_rupee,
|
||||||
ClaimsOverviewTheme.pink,
|
ClaimsOverviewTheme.pink,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
'Total Reimbursement',
|
'Total Reimbursement',
|
||||||
d.sidebarReimbursement,
|
d.sidebarReimbursement,
|
||||||
|
d.sidebarReimbursementPct,
|
||||||
Icons.account_balance_wallet_outlined,
|
Icons.account_balance_wallet_outlined,
|
||||||
ClaimsOverviewTheme.teal,
|
ClaimsOverviewTheme.teal,
|
||||||
),
|
),
|
||||||
(
|
|
||||||
'Reimbursement %',
|
|
||||||
d.sidebarReimbursementPct,
|
|
||||||
Icons.percent,
|
|
||||||
ClaimsOverviewTheme.orange,
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
'Cashless Claim Amount',
|
'Cashless Claim Amount',
|
||||||
d.sidebarCashless,
|
d.sidebarCashless,
|
||||||
|
d.sidebarCashlessPct,
|
||||||
Icons.shopping_bag_outlined,
|
Icons.shopping_bag_outlined,
|
||||||
ClaimsOverviewTheme.purple,
|
ClaimsOverviewTheme.purple,
|
||||||
),
|
),
|
||||||
(
|
|
||||||
'Cashless %',
|
|
||||||
d.sidebarCashlessPct,
|
|
||||||
Icons.pie_chart_outline,
|
|
||||||
ClaimsOverviewTheme.teal,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return _tabBodyWrapper(
|
return _tabBodyWrapper(
|
||||||
forPdfExport: widget.forPdfExport,
|
forPdfExport: widget.forPdfExport,
|
||||||
|
scrollable: widget.scrollable,
|
||||||
child: ClaimsTabPanelRow(
|
child: ClaimsTabPanelRow(
|
||||||
panels: [
|
panels: [
|
||||||
ClaimsTabPanel(
|
ClaimsTabPanel(
|
||||||
@ -339,8 +360,9 @@ class _PolicyExperienceTabState extends State<_PolicyExperienceTab>
|
|||||||
child: ClaimsMetricCard(
|
child: ClaimsMetricCard(
|
||||||
label: kpiMetrics[i].$1,
|
label: kpiMetrics[i].$1,
|
||||||
value: kpiMetrics[i].$2,
|
value: kpiMetrics[i].$2,
|
||||||
icon: kpiMetrics[i].$3,
|
secondaryValue: kpiMetrics[i].$3,
|
||||||
accentColor: kpiMetrics[i].$4,
|
icon: kpiMetrics[i].$4,
|
||||||
|
accentColor: kpiMetrics[i].$5,
|
||||||
replayToken: widget.replayToken,
|
replayToken: widget.replayToken,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -358,11 +380,13 @@ class _ClaimsAnalysisTab extends StatefulWidget {
|
|||||||
final int replayToken;
|
final int replayToken;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final bool forPdfExport;
|
final bool forPdfExport;
|
||||||
|
final bool scrollable;
|
||||||
|
|
||||||
const _ClaimsAnalysisTab({
|
const _ClaimsAnalysisTab({
|
||||||
required this.replayToken,
|
required this.replayToken,
|
||||||
required this.isActive,
|
required this.isActive,
|
||||||
this.forPdfExport = false,
|
this.forPdfExport = false,
|
||||||
|
this.scrollable = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -371,7 +395,7 @@ class _ClaimsAnalysisTab extends StatefulWidget {
|
|||||||
|
|
||||||
class _ClaimsAnalysisTabState extends State<_ClaimsAnalysisTab>
|
class _ClaimsAnalysisTabState extends State<_ClaimsAnalysisTab>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
static const _pairedChartHeight = 280.0;
|
double get _pairedChartHeight => widget.forPdfExport ? 210.0 : 280.0;
|
||||||
|
|
||||||
int _chartAnimKey = 0;
|
int _chartAnimKey = 0;
|
||||||
|
|
||||||
@ -416,7 +440,9 @@ class _ClaimsAnalysisTabState extends State<_ClaimsAnalysisTab>
|
|||||||
|
|
||||||
return _tabBodyWrapper(
|
return _tabBodyWrapper(
|
||||||
forPdfExport: widget.forPdfExport,
|
forPdfExport: widget.forPdfExport,
|
||||||
|
scrollable: widget.scrollable,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_chartRow(
|
_chartRow(
|
||||||
0,
|
0,
|
||||||
@ -515,7 +541,6 @@ class _ClaimsAnalysisTabState extends State<_ClaimsAnalysisTab>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (widget.forPdfExport) return row;
|
|
||||||
return IntrinsicHeight(child: row);
|
return IntrinsicHeight(child: row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -523,10 +548,12 @@ class _ClaimsAnalysisTabState extends State<_ClaimsAnalysisTab>
|
|||||||
class _DemographicsTab extends StatelessWidget {
|
class _DemographicsTab extends StatelessWidget {
|
||||||
final int replayToken;
|
final int replayToken;
|
||||||
final bool forPdfExport;
|
final bool forPdfExport;
|
||||||
|
final bool scrollable;
|
||||||
|
|
||||||
const _DemographicsTab({
|
const _DemographicsTab({
|
||||||
required this.replayToken,
|
required this.replayToken,
|
||||||
this.forPdfExport = false,
|
this.forPdfExport = false,
|
||||||
|
this.scrollable = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -548,6 +575,7 @@ class _DemographicsTab extends StatelessWidget {
|
|||||||
|
|
||||||
return _tabBodyWrapper(
|
return _tabBodyWrapper(
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (relationships.isNotEmpty) ...[
|
if (relationships.isNotEmpty) ...[
|
||||||
@ -633,10 +661,12 @@ class _DemographicsTab extends StatelessWidget {
|
|||||||
class _HospitalsTab extends StatelessWidget {
|
class _HospitalsTab extends StatelessWidget {
|
||||||
final int replayToken;
|
final int replayToken;
|
||||||
final bool forPdfExport;
|
final bool forPdfExport;
|
||||||
|
final bool scrollable;
|
||||||
|
|
||||||
const _HospitalsTab({
|
const _HospitalsTab({
|
||||||
required this.replayToken,
|
required this.replayToken,
|
||||||
this.forPdfExport = false,
|
this.forPdfExport = false,
|
||||||
|
this.scrollable = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -672,6 +702,7 @@ class _HospitalsTab extends StatelessWidget {
|
|||||||
|
|
||||||
return _tabBodyWrapper(
|
return _tabBodyWrapper(
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
IntrinsicHeight(
|
IntrinsicHeight(
|
||||||
@ -771,10 +802,12 @@ class _HospitalsTab extends StatelessWidget {
|
|||||||
class _SpecialtyTab extends StatelessWidget {
|
class _SpecialtyTab extends StatelessWidget {
|
||||||
final int replayToken;
|
final int replayToken;
|
||||||
final bool forPdfExport;
|
final bool forPdfExport;
|
||||||
|
final bool scrollable;
|
||||||
|
|
||||||
const _SpecialtyTab({
|
const _SpecialtyTab({
|
||||||
required this.replayToken,
|
required this.replayToken,
|
||||||
this.forPdfExport = false,
|
this.forPdfExport = false,
|
||||||
|
this.scrollable = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -800,6 +833,7 @@ class _SpecialtyTab extends StatelessWidget {
|
|||||||
|
|
||||||
return _tabBodyWrapper(
|
return _tabBodyWrapper(
|
||||||
forPdfExport: forPdfExport,
|
forPdfExport: forPdfExport,
|
||||||
|
scrollable: scrollable,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ClaimsTabPanelRow(
|
ClaimsTabPanelRow(
|
||||||
@ -933,11 +967,13 @@ class _EnrollmentTab extends StatefulWidget {
|
|||||||
final int replayToken;
|
final int replayToken;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final bool forPdfExport;
|
final bool forPdfExport;
|
||||||
|
final bool scrollable;
|
||||||
|
|
||||||
const _EnrollmentTab({
|
const _EnrollmentTab({
|
||||||
required this.replayToken,
|
required this.replayToken,
|
||||||
required this.isActive,
|
required this.isActive,
|
||||||
this.forPdfExport = false,
|
this.forPdfExport = false,
|
||||||
|
this.scrollable = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1161,6 +1197,7 @@ class _EnrollmentTabState extends State<_EnrollmentTab>
|
|||||||
|
|
||||||
return _tabBodyWrapper(
|
return _tabBodyWrapper(
|
||||||
forPdfExport: widget.forPdfExport,
|
forPdfExport: widget.forPdfExport,
|
||||||
|
scrollable: widget.scrollable,
|
||||||
child: widget.forPdfExport ? ClipRect(child: body) : body,
|
child: widget.forPdfExport ? ClipRect(child: body) : body,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -216,10 +216,14 @@ class ClaimsExperienceBody extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final wide = constraints.maxWidth >= 520;
|
||||||
final incurredTile = _OverviewInnerTile(
|
final incurredTile = _OverviewInnerTile(
|
||||||
label: 'Incurred Claims',
|
label: 'Incurred Claims',
|
||||||
icon: Icons.gps_fixed,
|
icon: Icons.gps_fixed,
|
||||||
accentColor: ClaimsOverviewTheme.red,
|
accentColor: ClaimsOverviewTheme.red,
|
||||||
|
matchRowHeight: wide,
|
||||||
child: _ClaimsExperienceDualValues(
|
child: _ClaimsExperienceDualValues(
|
||||||
primaryValue: incurredClaims,
|
primaryValue: incurredClaims,
|
||||||
secondaryValue: incurredRatio,
|
secondaryValue: incurredRatio,
|
||||||
@ -229,6 +233,7 @@ class ClaimsExperienceBody extends StatelessWidget {
|
|||||||
label: 'Projected Claims',
|
label: 'Projected Claims',
|
||||||
icon: Icons.trending_up,
|
icon: Icons.trending_up,
|
||||||
accentColor: ClaimsOverviewTheme.orange,
|
accentColor: ClaimsOverviewTheme.orange,
|
||||||
|
matchRowHeight: wide,
|
||||||
child: _ClaimsExperienceDualValues(
|
child: _ClaimsExperienceDualValues(
|
||||||
primaryValue: projectedClaims,
|
primaryValue: projectedClaims,
|
||||||
secondaryValue: projectedRatio,
|
secondaryValue: projectedRatio,
|
||||||
@ -238,20 +243,13 @@ class ClaimsExperienceBody extends StatelessWidget {
|
|||||||
label: 'Claims Incidence Rate',
|
label: 'Claims Incidence Rate',
|
||||||
icon: Icons.timeline,
|
icon: Icons.timeline,
|
||||||
accentColor: ClaimsOverviewTheme.cyan,
|
accentColor: ClaimsOverviewTheme.cyan,
|
||||||
child: Text(
|
matchRowHeight: wide,
|
||||||
claimsIncidenceRate,
|
child: _ClaimsExperienceDualValues(
|
||||||
style: GoogleFonts.poppins(
|
primaryValue: claimsIncidenceRate,
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: ClaimsOverviewTheme.textPrimary,
|
|
||||||
height: 1.25,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return LayoutBuilder(
|
if (!wide) {
|
||||||
builder: (context, constraints) {
|
|
||||||
if (constraints.maxWidth < 520) {
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
incurredTile,
|
incurredTile,
|
||||||
@ -263,6 +261,7 @@ class ClaimsExperienceBody extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return _OverviewThreeColumnRow(
|
return _OverviewThreeColumnRow(
|
||||||
|
equalizeHeight: true,
|
||||||
children: [incurredTile, projectedTile, incidenceTile],
|
children: [incurredTile, projectedTile, incidenceTile],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -282,6 +281,9 @@ class ClaimsInceptionBody extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final wide = constraints.maxWidth >= 400;
|
||||||
final valueStyle = GoogleFonts.poppins(
|
final valueStyle = GoogleFonts.poppins(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
@ -293,18 +295,18 @@ class ClaimsInceptionBody extends StatelessWidget {
|
|||||||
label: 'Inception Employee',
|
label: 'Inception Employee',
|
||||||
icon: Icons.person_add_alt,
|
icon: Icons.person_add_alt,
|
||||||
accentColor: ClaimsOverviewTheme.blue,
|
accentColor: ClaimsOverviewTheme.blue,
|
||||||
|
matchRowHeight: wide,
|
||||||
child: Text(inceptionEmployee, style: valueStyle),
|
child: Text(inceptionEmployee, style: valueStyle),
|
||||||
);
|
);
|
||||||
final livesTile = _OverviewInnerTile(
|
final livesTile = _OverviewInnerTile(
|
||||||
label: 'Inception Lives',
|
label: 'Inception Lives',
|
||||||
icon: Icons.favorite_border,
|
icon: Icons.favorite_border,
|
||||||
accentColor: ClaimsOverviewTheme.pink,
|
accentColor: ClaimsOverviewTheme.pink,
|
||||||
|
matchRowHeight: wide,
|
||||||
child: Text(inceptionLives, style: valueStyle),
|
child: Text(inceptionLives, style: valueStyle),
|
||||||
);
|
);
|
||||||
|
|
||||||
return LayoutBuilder(
|
if (!wide) {
|
||||||
builder: (context, constraints) {
|
|
||||||
if (constraints.maxWidth < 400) {
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
employeeTile,
|
employeeTile,
|
||||||
@ -314,6 +316,7 @@ class ClaimsInceptionBody extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return _OverviewTwoColumnRow(
|
return _OverviewTwoColumnRow(
|
||||||
|
equalizeHeight: true,
|
||||||
children: [employeeTile, livesTile],
|
children: [employeeTile, livesTile],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -348,12 +351,14 @@ class _OverviewInnerTile extends StatelessWidget {
|
|||||||
final IconData icon;
|
final IconData icon;
|
||||||
final Color accentColor;
|
final Color accentColor;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
final bool matchRowHeight;
|
||||||
|
|
||||||
const _OverviewInnerTile({
|
const _OverviewInnerTile({
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.accentColor,
|
required this.accentColor,
|
||||||
required this.child,
|
required this.child,
|
||||||
|
this.matchRowHeight = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -362,12 +367,14 @@ class _OverviewInnerTile extends StatelessWidget {
|
|||||||
accentColor: accentColor,
|
accentColor: accentColor,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
mainAxisSize: matchRowHeight ? MainAxisSize.max : MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(14, 10, 14, 8),
|
padding: const EdgeInsets.fromLTRB(14, 10, 14, 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
@ -401,6 +408,7 @@ class _OverviewInnerTile extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (matchRowHeight) const Spacer(),
|
||||||
Container(
|
Container(
|
||||||
height: 3,
|
height: 3,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -419,11 +427,11 @@ class _OverviewInnerTile extends StatelessWidget {
|
|||||||
|
|
||||||
class _ClaimsExperienceDualValues extends StatelessWidget {
|
class _ClaimsExperienceDualValues extends StatelessWidget {
|
||||||
final String primaryValue;
|
final String primaryValue;
|
||||||
final String secondaryValue;
|
final String? secondaryValue;
|
||||||
|
|
||||||
const _ClaimsExperienceDualValues({
|
const _ClaimsExperienceDualValues({
|
||||||
required this.primaryValue,
|
required this.primaryValue,
|
||||||
required this.secondaryValue,
|
this.secondaryValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -435,26 +443,27 @@ class _ClaimsExperienceDualValues extends StatelessWidget {
|
|||||||
height: 1.2,
|
height: 1.2,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Row(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Text(
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Text(
|
|
||||||
primaryValue,
|
primaryValue,
|
||||||
maxLines: 1,
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: valueStyle,
|
style: valueStyle,
|
||||||
),
|
),
|
||||||
),
|
if (secondaryValue != null && secondaryValue!.isNotEmpty) ...[
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
Text(
|
||||||
secondaryValue,
|
secondaryValue!,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: valueStyle.copyWith(fontSize: 16),
|
style: valueStyle.copyWith(fontSize: 16),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -546,34 +555,48 @@ class ClaimsPremiumMembershipBody extends StatelessWidget {
|
|||||||
/// Two equal columns — used for TPA / Insurer side-by-side rows.
|
/// Two equal columns — used for TPA / Insurer side-by-side rows.
|
||||||
class _OverviewTwoColumnRow extends StatelessWidget {
|
class _OverviewTwoColumnRow extends StatelessWidget {
|
||||||
final List<Widget> children;
|
final List<Widget> children;
|
||||||
|
final bool equalizeHeight;
|
||||||
|
|
||||||
const _OverviewTwoColumnRow({required this.children});
|
const _OverviewTwoColumnRow({
|
||||||
|
required this.children,
|
||||||
|
this.equalizeHeight = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
assert(children.length == 2);
|
assert(children.length == 2);
|
||||||
return Row(
|
final row = Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: equalizeHeight
|
||||||
|
? CrossAxisAlignment.stretch
|
||||||
|
: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: children[0]),
|
Expanded(child: children[0]),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(child: children[1]),
|
Expanded(child: children[1]),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
if (!equalizeHeight) return row;
|
||||||
|
return IntrinsicHeight(child: row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Three equal columns — matches Policy Information top-row alignment.
|
/// Three equal columns — matches Policy Information top-row alignment.
|
||||||
class _OverviewThreeColumnRow extends StatelessWidget {
|
class _OverviewThreeColumnRow extends StatelessWidget {
|
||||||
final List<Widget> children;
|
final List<Widget> children;
|
||||||
|
final bool equalizeHeight;
|
||||||
|
|
||||||
const _OverviewThreeColumnRow({required this.children});
|
const _OverviewThreeColumnRow({
|
||||||
|
required this.children,
|
||||||
|
this.equalizeHeight = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
assert(children.isNotEmpty && children.length <= 3);
|
assert(children.isNotEmpty && children.length <= 3);
|
||||||
return Row(
|
final row = Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: equalizeHeight
|
||||||
|
? CrossAxisAlignment.stretch
|
||||||
|
: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
for (var i = 0; i < 3; i++) ...[
|
for (var i = 0; i < 3; i++) ...[
|
||||||
if (i > 0) const SizedBox(width: 12),
|
if (i > 0) const SizedBox(width: 12),
|
||||||
@ -585,6 +608,8 @@ class _OverviewThreeColumnRow extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
if (!equalizeHeight) return row;
|
||||||
|
return IntrinsicHeight(child: row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -594,6 +619,7 @@ class ClaimsMetricCard extends StatelessWidget {
|
|||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
final String value;
|
final String value;
|
||||||
|
final String? secondaryValue;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final Color accentColor;
|
final Color accentColor;
|
||||||
final int replayToken;
|
final int replayToken;
|
||||||
@ -603,6 +629,7 @@ class ClaimsMetricCard extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.value,
|
required this.value,
|
||||||
|
this.secondaryValue,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.accentColor,
|
required this.accentColor,
|
||||||
this.replayToken = 0,
|
this.replayToken = 0,
|
||||||
@ -673,7 +700,8 @@ class ClaimsMetricCard extends StatelessWidget {
|
|||||||
height: compact ? 40 : 48,
|
height: compact ? 40 : 48,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: compact || !animateValue
|
child: secondaryValue == null
|
||||||
|
? (compact || !animateValue
|
||||||
? Text(
|
? Text(
|
||||||
value,
|
value,
|
||||||
style: valueStyle,
|
style: valueStyle,
|
||||||
@ -684,6 +712,30 @@ class ClaimsMetricCard extends StatelessWidget {
|
|||||||
value: value,
|
value: value,
|
||||||
replayToken: replayToken,
|
replayToken: replayToken,
|
||||||
style: valueStyle,
|
style: valueStyle,
|
||||||
|
))
|
||||||
|
: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: compact || !animateValue
|
||||||
|
? Text(
|
||||||
|
value,
|
||||||
|
style: valueStyle,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
)
|
||||||
|
: ClaimsAnimatedValueText(
|
||||||
|
value: value,
|
||||||
|
replayToken: replayToken,
|
||||||
|
style: valueStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
secondaryValue!,
|
||||||
|
style: valueStyle.copyWith(fontSize: compact ? 13 : 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -731,11 +783,15 @@ class ClaimsMetricCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ClaimsSectionCard extends StatelessWidget {
|
class ClaimsSectionCard extends StatelessWidget {
|
||||||
|
/// Min height for side-by-side overview pairs (e.g. Claims Experience / Inception).
|
||||||
|
static const double pairedSectionMinHeight = 170;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final bool fillHeight;
|
final bool fillHeight;
|
||||||
final double? height;
|
final double? height;
|
||||||
|
final double? minHeight;
|
||||||
|
|
||||||
const ClaimsSectionCard({
|
const ClaimsSectionCard({
|
||||||
super.key,
|
super.key,
|
||||||
@ -744,6 +800,7 @@ class ClaimsSectionCard extends StatelessWidget {
|
|||||||
required this.child,
|
required this.child,
|
||||||
this.fillHeight = false,
|
this.fillHeight = false,
|
||||||
this.height,
|
this.height,
|
||||||
|
this.minHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -773,12 +830,16 @@ class ClaimsSectionCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final useExpandedBody = fillHeight || height != null;
|
final useExpandedBody = height != null;
|
||||||
|
final stretchVertically = fillHeight || height != null;
|
||||||
final bodySlot = useExpandedBody ? Expanded(child: body) : body;
|
final bodySlot = useExpandedBody ? Expanded(child: body) : body;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: height,
|
height: height,
|
||||||
|
constraints: height == null && minHeight != null
|
||||||
|
? BoxConstraints(minHeight: minHeight!)
|
||||||
|
: null,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: ClaimsOverviewTheme.cardBg,
|
color: ClaimsOverviewTheme.cardBg,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@ -794,7 +855,7 @@ class ClaimsSectionCard extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisSize:
|
mainAxisSize:
|
||||||
useExpandedBody ? MainAxisSize.max : MainAxisSize.min,
|
stretchVertically ? MainAxisSize.max : MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
header,
|
header,
|
||||||
bodySlot,
|
bodySlot,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import 'package:universal_html/html.dart' as html;
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@ -448,6 +449,57 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> copyEcardLink(String? ecardLink) async {
|
||||||
|
if (ecardLink == null || ecardLink.trim().isEmpty) {
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showErrorToast(context, 'E-card link not available');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kIsWeb) {
|
||||||
|
await html.window.navigator.clipboard?.writeText(ecardLink);
|
||||||
|
} else {
|
||||||
|
await Clipboard.setData(ClipboardData(text: ecardLink));
|
||||||
|
}
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showSuccessToast(context, 'Link copied to clipboard');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sendEcardViaEmail(
|
||||||
|
dynamic empPolicyId,
|
||||||
|
dynamic clientPolicyId,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await apiService.sendMailForIndividualEmployeeEcard(
|
||||||
|
empPolicyId: empPolicyId,
|
||||||
|
clientPolicyId: clientPolicyId,
|
||||||
|
token: localToken!,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (response['status'] == true) {
|
||||||
|
ToastHelper.showSuccessToast(
|
||||||
|
context,
|
||||||
|
response['message']?.toString() ?? 'Mail sent successfully',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ToastHelper.showErrorToast(
|
||||||
|
context,
|
||||||
|
response['message']?.toString() ?? 'Failed to send mail.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logDebug('❌ sendEcardViaEmail error: $e');
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showErrorToast(
|
||||||
|
context,
|
||||||
|
'Failed to send mail. Please try again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _launchURL(String url) async {
|
Future<void> _launchURL(String url) async {
|
||||||
final Uri uri = Uri.parse(url); // Parse the URL properly
|
final Uri uri = Uri.parse(url); // Parse the URL properly
|
||||||
logDebug('_launchURL $uri');
|
logDebug('_launchURL $uri');
|
||||||
@ -530,6 +582,9 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
|||||||
}
|
}
|
||||||
|
|
||||||
filteredData = data.toList();
|
filteredData = data.toList();
|
||||||
|
hasAnyEcardLink = filteredData.any(
|
||||||
|
(item) => item['ecard_download_link'] != null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void search(String query) {
|
void search(String query) {
|
||||||
@ -640,6 +695,173 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dynamic _parseNumericId(dynamic value) {
|
||||||
|
if (value == null) return value;
|
||||||
|
return int.tryParse(value.toString()) ?? value;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _getInceptionExportStatus() {
|
||||||
|
switch (_preStatusFilter) {
|
||||||
|
case 'draft':
|
||||||
|
return ['draft'];
|
||||||
|
case 'enrolled':
|
||||||
|
return ['enrolled'];
|
||||||
|
default:
|
||||||
|
return ['enrolled'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
({String empCode, String empName}) _getInceptionSearchParams() {
|
||||||
|
final query = searchController.text.trim();
|
||||||
|
if (query.isEmpty) {
|
||||||
|
return (empCode: '', empName: '');
|
||||||
|
}
|
||||||
|
|
||||||
|
final lowerQuery = query.toLowerCase();
|
||||||
|
final codeMatch = originalData.any(
|
||||||
|
(row) =>
|
||||||
|
row['emp_code']?.toString().toLowerCase().contains(lowerQuery) ??
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
final nameMatch = originalData.any(
|
||||||
|
(row) =>
|
||||||
|
row['name']?.toString().toLowerCase().contains(lowerQuery) ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (codeMatch && !nameMatch) {
|
||||||
|
return (empCode: query, empName: '');
|
||||||
|
}
|
||||||
|
if (nameMatch && !codeMatch) {
|
||||||
|
return (empCode: '', empName: query);
|
||||||
|
}
|
||||||
|
return (empCode: query, empName: query);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> downloadInceptionExport() async {
|
||||||
|
try {
|
||||||
|
final clientId = await tokenService.readValue('enrollmentClient_id') ??
|
||||||
|
localClientId ??
|
||||||
|
widget.ClientId;
|
||||||
|
final branchId =
|
||||||
|
await tokenService.readValue('enrollmentEmpClientBranchId') ??
|
||||||
|
localClientBranchId ??
|
||||||
|
widget.clientBranchId;
|
||||||
|
final policyId = localClientPolicyId ?? widget.ClientPoliyId;
|
||||||
|
final searchParams = _getInceptionSearchParams();
|
||||||
|
final status = _getInceptionExportStatus();
|
||||||
|
|
||||||
|
logDebug(
|
||||||
|
'downloadInceptionExport client=$clientId branch=$branchId '
|
||||||
|
'policies=$policyId status=$status '
|
||||||
|
'empCode=${searchParams.empCode} empName=${searchParams.empName}',
|
||||||
|
);
|
||||||
|
|
||||||
|
final response = await apiService.downloadInception(
|
||||||
|
client: _parseNumericId(clientId),
|
||||||
|
branch: _parseNumericId(branchId),
|
||||||
|
policies: _parseNumericId(policyId),
|
||||||
|
status: status,
|
||||||
|
empCode: searchParams.empCode,
|
||||||
|
empName: searchParams.empName,
|
||||||
|
token: localToken!,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (response['status'] == true) {
|
||||||
|
final downloadUrl = response['data']?['downloadUrl'];
|
||||||
|
if (downloadUrl != null && downloadUrl.toString().isNotEmpty) {
|
||||||
|
await _launchURL(downloadUrl.toString());
|
||||||
|
ToastHelper.showSuccessToast(
|
||||||
|
context,
|
||||||
|
response['message']?.toString() ??
|
||||||
|
'Inception export generated successfully.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ToastHelper.showErrorToast(context, 'Download URL not available');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ToastHelper.showErrorToast(
|
||||||
|
context,
|
||||||
|
response['message']?.toString() ??
|
||||||
|
'Failed to generate inception export.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logDebug('downloadInceptionExport error: $e');
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showErrorToast(
|
||||||
|
context,
|
||||||
|
'Failed to generate inception export. Please try again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildExportButton() {
|
||||||
|
final buttonStyle = ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFFE26728),
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final textStyle = GoogleFonts.poppins(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.white,
|
||||||
|
letterSpacing: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (localTokenType == 'pre') {
|
||||||
|
return SizedBox(
|
||||||
|
width: 116,
|
||||||
|
height: 37,
|
||||||
|
child: PopupMenuButton<String>(
|
||||||
|
offset: const Offset(0, 37),
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == 'csv') {
|
||||||
|
exportToCsv(filteredData);
|
||||||
|
} else if (value == 'inception') {
|
||||||
|
downloadInceptionExport();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'csv',
|
||||||
|
child: Text(
|
||||||
|
'Export',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'inception',
|
||||||
|
child: Text(
|
||||||
|
'Inception Export',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {},
|
||||||
|
style: buttonStyle,
|
||||||
|
child: Text('Export', style: textStyle),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: 116,
|
||||||
|
height: 37,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => exportToCsv(filteredData),
|
||||||
|
style: buttonStyle,
|
||||||
|
child: Text('Export', style: textStyle),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String _capitalize(String? value) {
|
String _capitalize(String? value) {
|
||||||
if (value == null || value.isEmpty) return '';
|
if (value == null || value.isEmpty) return '';
|
||||||
return value[0].toUpperCase() + value.substring(1).toLowerCase();
|
return value[0].toUpperCase() + value.substring(1).toLowerCase();
|
||||||
@ -1446,31 +1668,7 @@ HR Team''';
|
|||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
/// ⬇️ Export Button
|
/// ⬇️ Export Button
|
||||||
SizedBox(
|
_buildExportButton(),
|
||||||
width: 116,
|
|
||||||
height: 37,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
exportToCsv(filteredData);
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFFE26728),
|
|
||||||
elevation: 0,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'Export',
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Colors.white,
|
|
||||||
letterSpacing: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
@ -2143,7 +2341,7 @@ HR Team''';
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
/// --- eCard Button (Fixed Space) ---
|
/// --- eCard Menu (Fixed Space) ---
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
@ -2152,19 +2350,58 @@ HR Team''';
|
|||||||
maintainSize: true,
|
maintainSize: true,
|
||||||
maintainAnimation: true,
|
maintainAnimation: true,
|
||||||
maintainState: true,
|
maintainState: true,
|
||||||
child: Tooltip(
|
child: PopupMenuButton<String>(
|
||||||
message: 'Download e-Card',
|
tooltip: 'Ecard',
|
||||||
child: MouseRegion(
|
offset: const Offset(0, 40),
|
||||||
cursor: SystemMouseCursors.click,
|
onSelected: (value) {
|
||||||
child: GestureDetector(
|
switch (value) {
|
||||||
onTap: () {
|
case 'download':
|
||||||
getEcardDownload(
|
getEcardDownload(
|
||||||
item['emp_code'],
|
item['emp_code'],
|
||||||
item['employee_id'],
|
item['employee_id'],
|
||||||
item['client_policy_id'],
|
item['client_policy_id'],
|
||||||
item['policy_no'],
|
item['policy_no'],
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
|
case 'email':
|
||||||
|
sendEcardViaEmail(
|
||||||
|
item['id'],
|
||||||
|
item['client_policy_id'],
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'copy':
|
||||||
|
copyEcardLink(
|
||||||
|
item['ecard_download_link']?.toString(),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'download',
|
||||||
|
child: Text(
|
||||||
|
'Download Ecard',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isSelf)
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'email',
|
||||||
|
child: Text(
|
||||||
|
'Send E-card via email',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'copy',
|
||||||
|
child: Text(
|
||||||
|
'Copy link',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFE6F5F6),
|
color: const Color(0xFFE6F5F6),
|
||||||
@ -2180,7 +2417,6 @@ HR Team''';
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|
||||||
|
|||||||
@ -874,6 +874,73 @@ class ApiService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> sendMailForIndividualEmployeeEcard({
|
||||||
|
required dynamic empPolicyId,
|
||||||
|
required dynamic clientPolicyId,
|
||||||
|
required String token,
|
||||||
|
}) async {
|
||||||
|
final url = Uri.parse(
|
||||||
|
'${Environment.apiUrlPost}sendMailForIndividualEmployeeEcard');
|
||||||
|
final headers = {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'APP-SIGNATURE':
|
||||||
|
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
|
||||||
|
};
|
||||||
|
final body = {
|
||||||
|
'emp_policy_id': empPolicyId,
|
||||||
|
'client_policy_id': clientPolicyId,
|
||||||
|
};
|
||||||
|
final response = await http.post(
|
||||||
|
url,
|
||||||
|
headers: headers,
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return jsonDecode(response.body);
|
||||||
|
} else {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to send e-card mail: ${response.statusCode} ${response.body}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> downloadInception({
|
||||||
|
required dynamic client,
|
||||||
|
required dynamic branch,
|
||||||
|
required dynamic policies,
|
||||||
|
required List<String> status,
|
||||||
|
required String empCode,
|
||||||
|
required String empName,
|
||||||
|
required String token,
|
||||||
|
}) async {
|
||||||
|
final url = Uri.parse('${Environment.apiUrl}download_inception');
|
||||||
|
final headers = {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'APP-SIGNATURE':
|
||||||
|
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
|
||||||
|
};
|
||||||
|
final body = {
|
||||||
|
'client': client,
|
||||||
|
'branch': branch,
|
||||||
|
'policies': policies,
|
||||||
|
'status': status,
|
||||||
|
'empCode': empCode,
|
||||||
|
'empName': empName,
|
||||||
|
};
|
||||||
|
final response = await http.post(
|
||||||
|
url,
|
||||||
|
headers: headers,
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return jsonDecode(response.body);
|
||||||
|
} else {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to download inception export: ${response.statusCode} ${response.body}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getNonEBClaimPoliciesToApi(
|
Future<Map<String, dynamic>> getNonEBClaimPoliciesToApi(
|
||||||
String token, request) async {
|
String token, request) async {
|
||||||
final url = Uri.parse('${Environment.apiUrlPost}api/v1/non-eb-claim/policies');
|
final url = Uri.parse('${Environment.apiUrlPost}api/v1/non-eb-claim/policies');
|
||||||
|
|||||||
20
web/flutter_bootstrap.js
Normal file
20
web/flutter_bootstrap.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{{flutter_js}}
|
||||||
|
{{flutter_build_config}}
|
||||||
|
|
||||||
|
const loadingIndicator = document.getElementById('loading_indicator');
|
||||||
|
|
||||||
|
_flutter.loader.load({
|
||||||
|
config: {
|
||||||
|
// CanvasKit is required for reliable chart capture (`toImage`) in PDF export.
|
||||||
|
renderer: 'canvaskit',
|
||||||
|
},
|
||||||
|
onEntrypointLoaded: async function (engineInitializer) {
|
||||||
|
const appRunner = await engineInitializer.initializeEngine({
|
||||||
|
renderer: 'canvaskit',
|
||||||
|
});
|
||||||
|
await appRunner.runApp();
|
||||||
|
if (loadingIndicator) {
|
||||||
|
loadingIndicator.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -80,47 +80,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
|
||||||
// The value below is injected by flutter build, do not touch.
|
|
||||||
const serviceWorkerVersion = null;
|
|
||||||
</script>
|
|
||||||
<!-- This script adds the flutter initialization JS code -->
|
|
||||||
<script src="flutter.js" defer></script>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body style="overflow:hidden">
|
<body style="overflow:hidden">
|
||||||
<div id="loading_indicator" class="container overlay">
|
<div id="loading_indicator" class="container overlay">
|
||||||
<img class="indicator" src="assets/nhance-loader.gif">
|
<img class="indicator" src="assets/nhance-loader.gif">
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script src="flutter_bootstrap.js" async></script>
|
||||||
window.addEventListener('load', function(ev) {
|
|
||||||
// Download main.dart.js
|
|
||||||
_flutter.loader.loadEntrypoint({
|
|
||||||
serviceWorker: {
|
|
||||||
serviceWorkerVersion: serviceWorkerVersion,
|
|
||||||
},
|
|
||||||
onEntrypointLoaded: function(engineInitializer) {
|
|
||||||
// CanvasKit renderer is required for reliable chart capture (`toImage`)
|
|
||||||
// used by dashboard PDF export.
|
|
||||||
engineInitializer.initializeEngine({
|
|
||||||
renderer: "canvaskit"
|
|
||||||
}).then(function(appRunner) {
|
|
||||||
appRunner.runApp();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
window.onLoad = function(){
|
|
||||||
setTimeout(function () {
|
|
||||||
var loadingIndicator = document.getElementById("loading_indicator");
|
|
||||||
if(loadingIndicator){
|
|
||||||
loadingIndicator.remove();
|
|
||||||
}
|
|
||||||
},5000);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user