import 'package:flutter/material.dart'; import 'package:tm_app/core/theme/colors.dart'; /// A windowed numbered pagination widget that mirrors the Next.js /// implementation: ±10 page-button window around the current page, /// 1-based labels, prev/next chevrons. class WindowedPagination extends StatelessWidget { const WindowedPagination({ super.key, required this.currentPage, required this.totalPages, required this.onPageChanged, this.maxPageJump = 10, this.compact = false, }); /// 0-based absolute current page index. final int currentPage; /// Total number of pages (1-based count). final int totalPages; /// Called with the 0-based target page index. final ValueChanged onPageChanged; /// Half-width of the page-button window (web uses 10). final int maxPageJump; /// If true, renders smaller buttons (for mobile). final bool compact; @override Widget build(BuildContext context) { if (totalPages <= 1) { return const SizedBox.shrink(); } final clampedCurrent = currentPage.clamp(0, totalPages - 1); final windowStart = (clampedCurrent - maxPageJump).clamp(0, totalPages - 1); final windowEnd = (clampedCurrent + maxPageJump).clamp(0, totalPages - 1); final buttons = []; buttons.add( _NavButton( icon: Icons.chevron_left, enabled: clampedCurrent > 0, compact: compact, onTap: () => onPageChanged(clampedCurrent - 1), ), ); if (windowStart > 0) { buttons.add(_buildPageButton(0, clampedCurrent)); if (windowStart > 1) { buttons.add(const _Ellipsis()); } } for (var i = windowStart; i <= windowEnd; i++) { buttons.add(_buildPageButton(i, clampedCurrent)); } if (windowEnd < totalPages - 1) { if (windowEnd < totalPages - 2) { buttons.add(const _Ellipsis()); } buttons.add(_buildPageButton(totalPages - 1, clampedCurrent)); } buttons.add( _NavButton( icon: Icons.chevron_right, enabled: clampedCurrent < totalPages - 1, compact: compact, onTap: () => onPageChanged(clampedCurrent + 1), ), ); final gap = compact ? 4.0 : 6.0; return Padding( padding: EdgeInsets.symmetric(vertical: compact ? 8 : 12), child: LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( scrollDirection: Axis.horizontal, child: ConstrainedBox( constraints: BoxConstraints(minWidth: constraints.maxWidth), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: _withSpacing(buttons, gap), ), ), ); }, ), ); } Widget _buildPageButton(int pageIndex, int currentPageIndex) { final isActive = pageIndex == currentPageIndex; return _PageButton( label: '${pageIndex + 1}', isActive: isActive, compact: compact, onTap: isActive ? null : () => onPageChanged(pageIndex), ); } static List _withSpacing(List items, double gap) { final result = []; for (var i = 0; i < items.length; i++) { result.add(items[i]); if (i != items.length - 1) { result.add(SizedBox(width: gap)); } } return result; } } class _PageButton extends StatelessWidget { const _PageButton({ required this.label, required this.isActive, required this.compact, this.onTap, }); final String label; final bool isActive; final bool compact; final VoidCallback? onTap; @override Widget build(BuildContext context) { final size = compact ? 32.0 : 38.0; return SizedBox( width: size, height: size, child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(12), onTap: onTap, child: Container( alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), gradient: isActive ? const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppColors.mainBlue, AppColors.primaryColor], ) : null, color: isActive ? null : AppColors.grey0, border: Border.all( color: isActive ? Colors.transparent : AppColors.grey30, width: 1, ), boxShadow: isActive ? [ BoxShadow( color: AppColors.mainBlue.withValues(alpha: 0.25), blurRadius: 8, offset: const Offset(0, 2), ), ] : null, ), child: Text( label, style: TextStyle( fontSize: compact ? 12 : 13, fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, color: isActive ? Colors.white : AppColors.grey70, ), ), ), ), ), ); } } class _NavButton extends StatelessWidget { const _NavButton({ required this.icon, required this.enabled, required this.compact, required this.onTap, }); final IconData icon; final bool enabled; final bool compact; final VoidCallback onTap; @override Widget build(BuildContext context) { final size = compact ? 32.0 : 38.0; return SizedBox( width: size, height: size, child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(12), onTap: enabled ? onTap : null, child: Container( alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: AppColors.grey0, border: Border.all( color: enabled ? AppColors.grey30 : AppColors.grey20, width: 1, ), ), child: Icon( icon, size: compact ? 18 : 20, color: enabled ? AppColors.grey70 : AppColors.grey40, ), ), ), ), ); } } class _Ellipsis extends StatelessWidget { const _Ellipsis(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 2), child: Text( '…', style: TextStyle( fontSize: 14, color: AppColors.grey60, fontWeight: FontWeight.w500, ), ), ); } }