policy list page new design
This commit is contained in:
parent
d4706f1dd0
commit
b8bd8952ed
@ -911,23 +911,7 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
}
|
||||
|
||||
void _resetPreStatusFilter() {
|
||||
setState(() {
|
||||
_preStatusFilter = null;
|
||||
_currentPage = 1;
|
||||
});
|
||||
_searchDebounce?.cancel();
|
||||
final query = searchController.text.trim();
|
||||
if (query.isEmpty) {
|
||||
setState(() {
|
||||
_hasSearched = false;
|
||||
_isSearching = false;
|
||||
originalData = [];
|
||||
filteredData = [];
|
||||
getCDPolicies = [];
|
||||
});
|
||||
} else {
|
||||
_searchEmployeesViaApi(query);
|
||||
}
|
||||
_resetToLanding();
|
||||
}
|
||||
|
||||
Future<void> downloadEmployeeListExcel() async {
|
||||
@ -1745,6 +1729,21 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
}
|
||||
|
||||
@override
|
||||
void _resetToLanding() {
|
||||
_searchDebounce?.cancel();
|
||||
searchController.clear();
|
||||
setState(() {
|
||||
_preStatusFilter = null;
|
||||
_hasSearched = false;
|
||||
_isSearching = false;
|
||||
isLoading = false;
|
||||
originalData = [];
|
||||
filteredData = [];
|
||||
getCDPolicies = [];
|
||||
_currentPage = 1;
|
||||
});
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return BaseLayout(
|
||||
child: _buildContent(context),
|
||||
@ -1752,15 +1751,534 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
final showResults = _hasSearched || _isSearching;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF8FAFC),
|
||||
backgroundColor: Colors.white,
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 280),
|
||||
switchInCurve: Curves.easeOutCubic,
|
||||
switchOutCurve: Curves.easeInCubic,
|
||||
child: showResults
|
||||
? KeyedSubtree(
|
||||
key: const ValueKey('results'),
|
||||
child: _buildResultsWorkspace(),
|
||||
)
|
||||
: KeyedSubtree(
|
||||
key: const ValueKey('landing'),
|
||||
child: _buildGoogleLandingView(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String get _policyTitleLine =>
|
||||
'${localCardType ?? ''} - ${localCardPolicyNo ?? ''}'.trim();
|
||||
|
||||
String get _policySubtitleLine => localTokenType == 'pre'
|
||||
? '${localCardPolicyName ?? ''} (${localCardPolicyExpDate ?? ''})'
|
||||
: '${localCardInsurerName ?? ''} - ${localCardPolicyName ?? ''} (${localCardPolicyExpDate ?? ''})';
|
||||
|
||||
/// First-entry Google-style home: title → search → action icons → filter icons.
|
||||
Widget _buildGoogleLandingView() {
|
||||
return Stack(
|
||||
children: [
|
||||
// ===================== MAIN CONTENT =====================
|
||||
Column(
|
||||
Positioned(
|
||||
top: 12,
|
||||
left: 12,
|
||||
child: IconButton(
|
||||
tooltip: 'Previous Page',
|
||||
onPressed: () async {
|
||||
await clearPolicyStorage();
|
||||
if (!mounted) return;
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => policies()),
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
size: 18,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(24, 48, 24, 32),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 720),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
_policyTitleLine,
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xFF0F172A),
|
||||
fontSize: 34,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.15,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_policySubtitleLine,
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xFF64748B),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
_buildLandingSearchPill(),
|
||||
const SizedBox(height: 28),
|
||||
_buildLandingShortcutRow(
|
||||
items: _landingActionShortcuts(),
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
if (_isLoadingCards)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 12),
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xFF009195),
|
||||
),
|
||||
)
|
||||
else if (_filterChips.isNotEmpty)
|
||||
_buildLandingFilterMetricsRow(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLandingSearchPill() {
|
||||
return Container(
|
||||
height: 54,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.06),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
onChanged: search,
|
||||
onSubmitted: (_) {
|
||||
_searchDebounce?.cancel();
|
||||
_searchEmployees();
|
||||
},
|
||||
textInputAction: TextInputAction.search,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: const Color(0xFF0F172A),
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search by name, emp code, mobile, email…',
|
||||
hintStyle: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
color: const Color(0xFF94A3B8),
|
||||
),
|
||||
prefixIcon: const Icon(
|
||||
Icons.search_rounded,
|
||||
color: Color(0xFF64748B),
|
||||
),
|
||||
suffixIcon: searchController.text.isNotEmpty
|
||||
? IconButton(
|
||||
tooltip: 'Clear',
|
||||
onPressed: () {
|
||||
searchController.clear();
|
||||
setState(() {});
|
||||
},
|
||||
icon: const Icon(Icons.close_rounded, size: 18),
|
||||
)
|
||||
: null,
|
||||
border: InputBorder.none,
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<({String label, IconData icon, Color color, VoidCallback? onTap})>
|
||||
_landingActionShortcuts() {
|
||||
return [
|
||||
if (localTokenType == 'pre') ...[
|
||||
(
|
||||
label: 'Reminder',
|
||||
icon: Icons.tune_rounded,
|
||||
color: const Color(0xFF009195),
|
||||
onTap: _openReminderConfigDialog,
|
||||
),
|
||||
(
|
||||
label: 'Mail Template',
|
||||
icon: Icons.mail_outline_rounded,
|
||||
color: const Color(0xFFE26728),
|
||||
onTap: _isSendingReminder ? null : _openReminderTemplateDialog,
|
||||
),
|
||||
],
|
||||
if (localTokenType == 'post') ...[
|
||||
(
|
||||
label: 'Policy Terms',
|
||||
icon: Icons.description_outlined,
|
||||
color: const Color(0xFF009195),
|
||||
onTap: _isLoadingPolicyTerms ? null : _openPolicyTermsDialog,
|
||||
),
|
||||
(
|
||||
label: 'Hospitals',
|
||||
icon: Icons.local_hospital_outlined,
|
||||
color: const Color(0xFF009195),
|
||||
onTap: _isLoadingHospitalList ? null : _openNetworkHospitalListDialog,
|
||||
),
|
||||
],
|
||||
(
|
||||
label: 'Import',
|
||||
icon: Icons.upload_file_rounded,
|
||||
color: const Color(0xFFE26728),
|
||||
onTap: _openImportFlow,
|
||||
),
|
||||
(
|
||||
label: 'Export',
|
||||
icon: Icons.download_rounded,
|
||||
color: const Color(0xFFE26728),
|
||||
onTap: localTokenType == 'pre'
|
||||
? null
|
||||
: (_isExportingExcel ? null : downloadEmployeeListExcel),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<({String key, String label, int count, IconData icon, Color color})>
|
||||
_landingFilterMetrics() {
|
||||
return _filterChips.map((filter) {
|
||||
final key = filter['key'].toString();
|
||||
return (
|
||||
key: key,
|
||||
label: (filter['label'] ?? _chipLabelForKey(key)).toString(),
|
||||
count: int.tryParse('${filter['count']}') ?? 0,
|
||||
icon: _landingFilterIcon(key),
|
||||
color: _landingFilterColor(key),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Filter metrics with rounded-square icons and stylish dividers between.
|
||||
Widget _buildLandingFilterMetricsRow() {
|
||||
final items = _landingFilterMetrics();
|
||||
if (items.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (var i = 0; i < items.length; i++) ...[
|
||||
if (i > 0) _buildLandingFilterDivider(),
|
||||
_buildLandingFilterMetricTile(items[i]),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLandingFilterDivider() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 6),
|
||||
width: 1.5,
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x00CBD5E1),
|
||||
Color(0xFF94A3B8),
|
||||
Color(0x00CBD5E1),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLandingFilterMetricTile(
|
||||
({String key, String label, int count, IconData icon, Color color}) item,
|
||||
) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => _onPreStatusFilterTap(item.key),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 52,
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
color: item.color.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: item.color.withValues(alpha: 0.22),
|
||||
),
|
||||
),
|
||||
child: Icon(item.icon, color: item.color, size: 24),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${item.count}',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.1,
|
||||
color: const Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
item.label,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: const Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _landingFilterIcon(String key) {
|
||||
switch (key.toLowerCase()) {
|
||||
case 'emp_count':
|
||||
return Icons.groups_rounded;
|
||||
case 'submitted':
|
||||
return Icons.verified_rounded;
|
||||
case 'logged_in':
|
||||
return Icons.login_rounded;
|
||||
case 'not_logged_in':
|
||||
return Icons.person_off_rounded;
|
||||
case 'draft':
|
||||
return Icons.edit_note_rounded;
|
||||
default:
|
||||
return Icons.filter_alt_rounded;
|
||||
}
|
||||
}
|
||||
|
||||
Color _landingFilterColor(String key) {
|
||||
switch (key.toLowerCase()) {
|
||||
case 'emp_count':
|
||||
return const Color(0xFF3B82F6);
|
||||
case 'submitted':
|
||||
return const Color(0xFF16A34A);
|
||||
case 'logged_in':
|
||||
return const Color(0xFF009195);
|
||||
case 'not_logged_in':
|
||||
return const Color(0xFF6366F1);
|
||||
case 'draft':
|
||||
return const Color(0xFFE26728);
|
||||
default:
|
||||
return const Color(0xFF009195);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildLandingShortcutRow({
|
||||
required List<
|
||||
({String label, IconData icon, Color color, VoidCallback? onTap})>
|
||||
items,
|
||||
}) {
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 18,
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
for (final item in items)
|
||||
_buildLandingShortcutTile(
|
||||
label: item.label,
|
||||
icon: item.icon,
|
||||
color: item.color,
|
||||
onTap: item.onTap,
|
||||
isExportMenu: item.label == 'Export' && localTokenType == 'pre',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLandingShortcutTile({
|
||||
required String label,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
required VoidCallback? onTap,
|
||||
bool isExportMenu = false,
|
||||
}) {
|
||||
final tile = SizedBox(
|
||||
width: 84,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: color.withValues(alpha: 0.18)),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 24),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.2,
|
||||
color: const Color(0xFF334155),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (isExportMenu) {
|
||||
return PopupMenuButton<String>(
|
||||
tooltip: 'Export',
|
||||
offset: const Offset(0, 8),
|
||||
onSelected: (value) {
|
||||
if (value == 'csv') {
|
||||
downloadEmployeeListExcel();
|
||||
} else if (value == 'inception') {
|
||||
downloadInceptionExport();
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'csv',
|
||||
child: Text('Export Excel', style: GoogleFonts.poppins(fontSize: 13)),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'inception',
|
||||
child: Text(
|
||||
'Inception Export',
|
||||
style: GoogleFonts.poppins(fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: tile,
|
||||
);
|
||||
}
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: tile,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openImportFlow() async {
|
||||
await tokenService.writeValue(
|
||||
'upload_ClientId', widget.ClientId.toString());
|
||||
await tokenService.writeValue(
|
||||
'upload_policyTypeId', widget.policyTypeId.toString());
|
||||
await tokenService.writeValue(
|
||||
'upload_ClientPoliyId', widget.ClientPoliyId.toString());
|
||||
await tokenService.writeValue(
|
||||
'upload_clientBranchId', widget.clientBranchId.toString());
|
||||
await tokenService.writeValue('upload_Token', widget.Token.toString());
|
||||
await tokenService.writeValue(
|
||||
'upload_TokenType', widget.TokenType.toString());
|
||||
await tokenService.writeValue(
|
||||
'upload_cardType', widget.cardType.toString());
|
||||
await tokenService.writeValue(
|
||||
'upload_cardPolicyNo', widget.cardPolicyNo.toString());
|
||||
await tokenService.writeValue(
|
||||
'upload_cardInsurer_name', widget.cardInsurer_name.toString());
|
||||
await tokenService.writeValue(
|
||||
'upload_cardPolicy_name', widget.cardPolicy_name.toString());
|
||||
await tokenService.writeValue(
|
||||
'upload_cardPolicy_ExpDate', widget.cardPolicy_ExpDate.toString());
|
||||
await tokenService.writeValue(
|
||||
'upload_total_premium', widget.total_premium.toString());
|
||||
if (!mounted) return;
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
settings: localTokenType != "post"
|
||||
? const RouteSettings(name: 'preFileUpload')
|
||||
: const RouteSettings(name: 'postFileUpload'),
|
||||
builder: (context) => localTokenType != "post"
|
||||
? preFileUpload(
|
||||
ClientId: widget.ClientId,
|
||||
policyTypeId: widget.policyTypeId,
|
||||
ClientPoliyId: widget.ClientPoliyId,
|
||||
clientBranchId: widget.clientBranchId,
|
||||
Token: widget.Token,
|
||||
TokenType: widget.TokenType,
|
||||
cardType: widget.cardType,
|
||||
cardPolicyNo: widget.cardPolicyNo,
|
||||
cardInsurer_name: widget.cardInsurer_name,
|
||||
cardPolicy_name: widget.cardPolicy_name,
|
||||
cardPolicy_ExpDate: widget.cardPolicy_ExpDate,
|
||||
total_premium: widget.total_premium,
|
||||
)
|
||||
: postFileUpload(
|
||||
ClientId: widget.ClientId,
|
||||
policyTypeId: widget.policyTypeId,
|
||||
ClientPoliyId: widget.ClientPoliyId,
|
||||
clientBranchId: widget.clientBranchId,
|
||||
Token: widget.Token,
|
||||
TokenType: widget.TokenType,
|
||||
cardType: widget.cardType,
|
||||
cardPolicyNo: widget.cardPolicyNo,
|
||||
cardInsurer_name: widget.cardInsurer_name,
|
||||
cardPolicy_name: widget.cardPolicy_name,
|
||||
cardPolicy_ExpDate: widget.cardPolicy_ExpDate,
|
||||
total_premium: widget.total_premium,
|
||||
allocgType: 'EB',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Current design: header actions + find members + table.
|
||||
Widget _buildResultsWorkspace() {
|
||||
return Column(
|
||||
children: [
|
||||
// ---------------- HEADER ----------------
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
child: LayoutBuilder(
|
||||
@ -1770,19 +2288,11 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: 'Previous Page',
|
||||
onPressed: () async => {
|
||||
await clearPolicyStorage(),
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => policies(),
|
||||
),
|
||||
)
|
||||
},
|
||||
tooltip: 'Back to home',
|
||||
onPressed: _resetToLanding,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
size: 18,
|
||||
@ -1797,8 +2307,9 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${localCardType} - ${localCardPolicyNo} " ??
|
||||
'',
|
||||
_policyTitleLine,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xFF0F172A),
|
||||
fontSize: isCompact ? 15 : 17,
|
||||
@ -1807,9 +2318,9 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
localTokenType == 'pre'
|
||||
? "${localCardPolicyName} (${localCardPolicyExpDate})"
|
||||
: "${localCardInsurerName} - ${localCardPolicyName} (${localCardPolicyExpDate})",
|
||||
_policySubtitleLine,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xFF64748B),
|
||||
fontSize: 12,
|
||||
@ -1819,22 +2330,22 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
const SizedBox(width: 12),
|
||||
Flexible(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.end,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
if (localIsEcardBulkDownload == 1)
|
||||
SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
getEcardBulkDownload();
|
||||
},
|
||||
onPressed: getEcardBulkDownload,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE26728),
|
||||
elevation: 0,
|
||||
@ -1855,13 +2366,13 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
_buildHeaderActionButton(
|
||||
label: 'Reminder Config',
|
||||
color: const Color(0xFF009195),
|
||||
width: 150,
|
||||
width: isCompact ? 130 : 150,
|
||||
onPressed: _openReminderConfigDialog,
|
||||
),
|
||||
_buildHeaderActionButton(
|
||||
label: 'Reminder Mail Template',
|
||||
color: const Color(0xFFE26728),
|
||||
width: 210,
|
||||
width: isCompact ? 170 : 210,
|
||||
onPressed: _isSendingReminder
|
||||
? null
|
||||
: _openReminderTemplateDialog,
|
||||
@ -1892,15 +2403,16 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
_buildExportButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// ---------------- SEARCH + FILTER CHIPS ----------------
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: LayoutBuilder(
|
||||
@ -1909,10 +2421,7 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// ---------------- TABLE (after search only) ----------------
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
@ -1929,24 +2438,44 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
'assets/nhance-loader.gif',
|
||||
),
|
||||
)
|
||||
: !_hasSearched
|
||||
? KeyedSubtree(
|
||||
key: const ValueKey('prompt'),
|
||||
child: _buildSearchPromptEmptyState(),
|
||||
)
|
||||
: filteredData.isEmpty
|
||||
? Center(
|
||||
key: const ValueKey('empty'),
|
||||
child: Text(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'No matching members found',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
color: const Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: _resetToLanding,
|
||||
child: Text(
|
||||
'Back to home',
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF009195),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: DecoratedBox(
|
||||
: LayoutBuilder(
|
||||
key: const ValueKey('table'),
|
||||
builder: (context, constraints) {
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: constraints.maxHeight,
|
||||
minWidth: constraints.maxWidth,
|
||||
),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@ -1964,23 +2493,18 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildCDDataTable(context),
|
||||
child: _buildCDDataTable(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 48),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -2669,22 +3193,23 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
}
|
||||
|
||||
Widget _buildCDDataTable(BuildContext context) {
|
||||
return Expanded(
|
||||
child: CustomScrollView(
|
||||
final showUHID = localPolicyTypeId != '6' && localPolicyTypeId != '7';
|
||||
final showLoggedIn = localTokenType == 'pre';
|
||||
final showAction =
|
||||
localTokenType != 'pre' && (hasAnyEcardLink || hasModule);
|
||||
|
||||
return CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
slivers: [
|
||||
/// 🔒 Sticky Header
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: _CDHeaderDelegate(
|
||||
showUHID: localPolicyTypeId != '6' && localPolicyTypeId != '7',
|
||||
showUHID: showUHID,
|
||||
uhidHeaderLabel: localTokenType == 'post' ? 'TPA ID' : 'UHID',
|
||||
showLoggedIn: localTokenType == "pre",
|
||||
showAction:
|
||||
localTokenType != "pre" && (hasAnyEcardLink || hasModule),
|
||||
showLoggedIn: showLoggedIn,
|
||||
showAction: showAction,
|
||||
),
|
||||
),
|
||||
|
||||
/// 📄 Rows
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
@ -2694,13 +3219,16 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
childCount: _paginatedData.length,
|
||||
),
|
||||
),
|
||||
|
||||
/// 📌 Pagination
|
||||
SliverToBoxAdapter(
|
||||
child: _buildPagination(context),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Divider(height: 1, thickness: 1, color: Color(0xFFE2E8F0)),
|
||||
_buildPagination(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -3589,35 +4117,44 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
List<int> visiblePages = getVisiblePages();
|
||||
|
||||
return Padding(
|
||||
// Match this horizontal padding (16) to your Table Header padding for perfect alignment
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
padding: const EdgeInsets.fromLTRB(12, 6, 12, 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment
|
||||
.spaceBetween, // Pushes text to left, buttons to right
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// --- LEFT SIDE: Showing Text ---
|
||||
Text(
|
||||
Flexible(
|
||||
child: Text(
|
||||
"Showing $startEntry to $endEntry of $totalItems entries",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 13,
|
||||
fontSize: 12,
|
||||
color: const Color(0xFF585757),
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
// --- RIGHT SIDE: Controls ---
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Dropdown for rows per page
|
||||
DropdownButton<int>(
|
||||
Text(
|
||||
'Rows',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
color: const Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton<int>(
|
||||
value: _rowsPerPage,
|
||||
focusColor: Colors
|
||||
.transparent, // Fix: Removes the grey/blue highlight on change
|
||||
isDense: true,
|
||||
focusColor: Colors.transparent,
|
||||
items: [5, 10, 15, 20, 50].map((int value) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: value,
|
||||
child: Text(' $value ',
|
||||
style: GoogleFonts.poppins(fontSize: 15)),
|
||||
child: Text(
|
||||
'$value',
|
||||
style: GoogleFonts.poppins(fontSize: 13),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (newValue) {
|
||||
@ -3627,47 +4164,43 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(width: 8),
|
||||
|
||||
// Previous button
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Previous Page',
|
||||
visualDensity: VisualDensity.compact,
|
||||
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: _currentPage > 1
|
||||
? () => setState(() => _currentPage--)
|
||||
: null,
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
icon: const Icon(Icons.chevron_left, size: 20),
|
||||
),
|
||||
|
||||
// First page + left ellipsis
|
||||
if (!visiblePages.contains(1))
|
||||
Row(children: [
|
||||
_buildPageButton(1),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
child: Text("..."),
|
||||
),
|
||||
]),
|
||||
|
||||
// Visible page buttons
|
||||
for (int page in visiblePages) _buildPageButton(page),
|
||||
|
||||
// Right ellipsis + last page
|
||||
if (!visiblePages.contains(totalPages) && totalPages > 0)
|
||||
Row(children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
child: Text("..."),
|
||||
),
|
||||
_buildPageButton(totalPages),
|
||||
]),
|
||||
|
||||
// Next button
|
||||
IconButton(
|
||||
tooltip: 'Next Page',
|
||||
visualDensity: VisualDensity.compact,
|
||||
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: _currentPage < totalPages
|
||||
? () => setState(() => _currentPage++)
|
||||
: null,
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
icon: const Icon(Icons.chevron_right, size: 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -3678,21 +4211,32 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
||||
|
||||
Widget _buildPageButton(int page) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
child: SizedBox(
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
_currentPage == page ? const Color(0xFF00A6A6) : Colors.grey[300],
|
||||
foregroundColor: _currentPage == page ? Colors.white : Colors.black,
|
||||
minimumSize: const Size(36, 36),
|
||||
backgroundColor: _currentPage == page
|
||||
? const Color(0xFF00A6A6)
|
||||
: Colors.grey[300],
|
||||
foregroundColor:
|
||||
_currentPage == page ? Colors.white : Colors.black,
|
||||
minimumSize: const Size(30, 30),
|
||||
padding: EdgeInsets.zero,
|
||||
elevation: 0,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_currentPage = page;
|
||||
});
|
||||
},
|
||||
child: Text(page.toString()),
|
||||
child: Text(
|
||||
page.toString(),
|
||||
style: GoogleFonts.poppins(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user