policy list page new design

This commit is contained in:
Surendiran 2026-08-04 10:24:05 +05:30
parent d4706f1dd0
commit b8bd8952ed

View File

@ -911,23 +911,7 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
} }
void _resetPreStatusFilter() { void _resetPreStatusFilter() {
setState(() { _resetToLanding();
_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);
}
} }
Future<void> downloadEmployeeListExcel() async { Future<void> downloadEmployeeListExcel() async {
@ -1745,6 +1729,21 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
} }
@override @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) { Widget build(BuildContext context) {
return BaseLayout( return BaseLayout(
child: _buildContent(context), child: _buildContent(context),
@ -1752,15 +1751,534 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
} }
Widget _buildContent(BuildContext context) { Widget _buildContent(BuildContext context) {
final showResults = _hasSearched || _isSearching;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF8FAFC), backgroundColor: Colors.white,
body: SafeArea( 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: [ children: [
// ===================== MAIN CONTENT ===================== Positioned(
Column( 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: [ children: [
// ---------------- HEADER ----------------
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: LayoutBuilder( child: LayoutBuilder(
@ -1770,19 +2288,11 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
tooltip: 'Previous Page', tooltip: 'Back to home',
onPressed: () async => { onPressed: _resetToLanding,
await clearPolicyStorage(),
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => policies(),
),
)
},
icon: const Icon( icon: const Icon(
Icons.arrow_back_ios_new_rounded, Icons.arrow_back_ios_new_rounded,
size: 18, size: 18,
@ -1797,8 +2307,9 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"${localCardType} - ${localCardPolicyNo} " ?? _policyTitleLine,
'', maxLines: 1,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
color: const Color(0xFF0F172A), color: const Color(0xFF0F172A),
fontSize: isCompact ? 15 : 17, fontSize: isCompact ? 15 : 17,
@ -1807,9 +2318,9 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
localTokenType == 'pre' _policySubtitleLine,
? "${localCardPolicyName} (${localCardPolicyExpDate})" maxLines: 1,
: "${localCardInsurerName} - ${localCardPolicyName} (${localCardPolicyExpDate})", overflow: TextOverflow.ellipsis,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
color: const Color(0xFF64748B), color: const Color(0xFF64748B),
fontSize: 12, fontSize: 12,
@ -1819,22 +2330,22 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
], ],
), ),
), ),
], const SizedBox(width: 12),
), Flexible(
const SizedBox(height: 14), child: Align(
Wrap( alignment: Alignment.centerRight,
spacing: 10, child: Wrap(
runSpacing: 10, spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.end, alignment: WrapAlignment.end,
crossAxisAlignment: WrapCrossAlignment.center,
children: [ children: [
if (localIsEcardBulkDownload == 1) if (localIsEcardBulkDownload == 1)
SizedBox( SizedBox(
width: 40, width: 40,
height: 40, height: 40,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: getEcardBulkDownload,
getEcardBulkDownload();
},
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFE26728), backgroundColor: const Color(0xFFE26728),
elevation: 0, elevation: 0,
@ -1855,13 +2366,13 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
_buildHeaderActionButton( _buildHeaderActionButton(
label: 'Reminder Config', label: 'Reminder Config',
color: const Color(0xFF009195), color: const Color(0xFF009195),
width: 150, width: isCompact ? 130 : 150,
onPressed: _openReminderConfigDialog, onPressed: _openReminderConfigDialog,
), ),
_buildHeaderActionButton( _buildHeaderActionButton(
label: 'Reminder Mail Template', label: 'Reminder Mail Template',
color: const Color(0xFFE26728), color: const Color(0xFFE26728),
width: 210, width: isCompact ? 170 : 210,
onPressed: _isSendingReminder onPressed: _isSendingReminder
? null ? null
: _openReminderTemplateDialog, : _openReminderTemplateDialog,
@ -1892,15 +2403,16 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
_buildExportButton(), _buildExportButton(),
], ],
), ),
),
),
],
),
], ],
); );
}, },
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// ---------------- SEARCH + FILTER CHIPS ----------------
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: LayoutBuilder( child: LayoutBuilder(
@ -1909,10 +2421,7 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
}, },
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// ---------------- TABLE (after search only) ----------------
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
@ -1929,24 +2438,44 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
'assets/nhance-loader.gif', 'assets/nhance-loader.gif',
), ),
) )
: !_hasSearched
? KeyedSubtree(
key: const ValueKey('prompt'),
child: _buildSearchPromptEmptyState(),
)
: filteredData.isEmpty : filteredData.isEmpty
? Center( ? Center(
key: const ValueKey('empty'), key: const ValueKey('empty'),
child: Text( child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'No matching members found', 'No matching members found',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: const Color(0xFF64748B), 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'), 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( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
@ -1964,23 +2493,18 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
child: Column( child: _buildCDDataTable(context),
children: [ ),
_buildCDDataTable(context), ),
),
);
},
),
),
),
),
const SizedBox(height: 24),
], ],
),
),
),
),
),
),
const SizedBox(height: 48),
],
),
],
),
),
); );
} }
@ -2669,22 +3193,23 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
} }
Widget _buildCDDataTable(BuildContext context) { Widget _buildCDDataTable(BuildContext context) {
return Expanded( final showUHID = localPolicyTypeId != '6' && localPolicyTypeId != '7';
child: CustomScrollView( final showLoggedIn = localTokenType == 'pre';
final showAction =
localTokenType != 'pre' && (hasAnyEcardLink || hasModule);
return CustomScrollView(
shrinkWrap: true,
slivers: [ slivers: [
/// 🔒 Sticky Header
SliverPersistentHeader( SliverPersistentHeader(
pinned: true, pinned: true,
delegate: _CDHeaderDelegate( delegate: _CDHeaderDelegate(
showUHID: localPolicyTypeId != '6' && localPolicyTypeId != '7', showUHID: showUHID,
uhidHeaderLabel: localTokenType == 'post' ? 'TPA ID' : 'UHID', uhidHeaderLabel: localTokenType == 'post' ? 'TPA ID' : 'UHID',
showLoggedIn: localTokenType == "pre", showLoggedIn: showLoggedIn,
showAction: showAction: showAction,
localTokenType != "pre" && (hasAnyEcardLink || hasModule),
), ),
), ),
/// 📄 Rows
SliverList( SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
@ -2694,13 +3219,16 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
childCount: _paginatedData.length, childCount: _paginatedData.length,
), ),
), ),
/// 📌 Pagination
SliverToBoxAdapter( 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(); List<int> visiblePages = getVisiblePages();
return Padding( return Padding(
// Match this horizontal padding (16) to your Table Header padding for perfect alignment padding: const EdgeInsets.fromLTRB(12, 6, 12, 6),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment mainAxisAlignment: MainAxisAlignment.spaceBetween,
.spaceBetween, // Pushes text to left, buttons to right
children: [ children: [
// --- LEFT SIDE: Showing Text --- Flexible(
Text( child: Text(
"Showing $startEntry to $endEntry of $totalItems entries", "Showing $startEntry to $endEntry of $totalItems entries",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 13, fontSize: 12,
color: const Color(0xFF585757), color: const Color(0xFF585757),
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
overflow: TextOverflow.ellipsis,
),
), ),
// --- RIGHT SIDE: Controls ---
Row( Row(
mainAxisSize: MainAxisSize.min,
children: [ children: [
// Dropdown for rows per page Text(
DropdownButton<int>( 'Rows',
style: GoogleFonts.poppins(
fontSize: 12,
color: const Color(0xFF64748B),
),
),
const SizedBox(width: 6),
DropdownButtonHideUnderline(
child: DropdownButton<int>(
value: _rowsPerPage, value: _rowsPerPage,
focusColor: Colors isDense: true,
.transparent, // Fix: Removes the grey/blue highlight on change focusColor: Colors.transparent,
items: [5, 10, 15, 20, 50].map((int value) { items: [5, 10, 15, 20, 50].map((int value) {
return DropdownMenuItem<int>( return DropdownMenuItem<int>(
value: value, value: value,
child: Text(' $value ', child: Text(
style: GoogleFonts.poppins(fontSize: 15)), '$value',
style: GoogleFonts.poppins(fontSize: 13),
),
); );
}).toList(), }).toList(),
onChanged: (newValue) { onChanged: (newValue) {
@ -3627,47 +4164,43 @@ class _HrPolicyDetailsState extends State<hrPolicyDetails>
}); });
}, },
), ),
),
const SizedBox(width: 8),
// Previous button
IconButton( IconButton(
tooltip: 'Previous Page', tooltip: 'Previous Page',
visualDensity: VisualDensity.compact,
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
padding: EdgeInsets.zero,
onPressed: _currentPage > 1 onPressed: _currentPage > 1
? () => setState(() => _currentPage--) ? () => setState(() => _currentPage--)
: null, : null,
icon: const Icon(Icons.chevron_left), icon: const Icon(Icons.chevron_left, size: 20),
), ),
// First page + left ellipsis
if (!visiblePages.contains(1)) if (!visiblePages.contains(1))
Row(children: [ Row(children: [
_buildPageButton(1), _buildPageButton(1),
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 4), padding: EdgeInsets.symmetric(horizontal: 2),
child: Text("..."), child: Text("..."),
), ),
]), ]),
// Visible page buttons
for (int page in visiblePages) _buildPageButton(page), for (int page in visiblePages) _buildPageButton(page),
// Right ellipsis + last page
if (!visiblePages.contains(totalPages) && totalPages > 0) if (!visiblePages.contains(totalPages) && totalPages > 0)
Row(children: [ Row(children: [
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 4), padding: EdgeInsets.symmetric(horizontal: 2),
child: Text("..."), child: Text("..."),
), ),
_buildPageButton(totalPages), _buildPageButton(totalPages),
]), ]),
// Next button
IconButton( IconButton(
tooltip: 'Next Page',
visualDensity: VisualDensity.compact,
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
padding: EdgeInsets.zero,
onPressed: _currentPage < totalPages onPressed: _currentPage < totalPages
? () => setState(() => _currentPage++) ? () => setState(() => _currentPage++)
: null, : 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) { Widget _buildPageButton(int page) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(horizontal: 2),
child: SizedBox(
width: 30,
height: 30,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor: _currentPage == page
_currentPage == page ? const Color(0xFF00A6A6) : Colors.grey[300], ? const Color(0xFF00A6A6)
foregroundColor: _currentPage == page ? Colors.white : Colors.black, : Colors.grey[300],
minimumSize: const Size(36, 36), foregroundColor:
_currentPage == page ? Colors.white : Colors.black,
minimumSize: const Size(30, 30),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
elevation: 0,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
), ),
onPressed: () { onPressed: () {
setState(() { setState(() {
_currentPage = page; _currentPage = page;
}); });
}, },
child: Text(page.toString()), child: Text(
page.toString(),
style: GoogleFonts.poppins(fontSize: 12),
),
),
), ),
); );
} }